이번 장은 클린코드의 17장 '냄새와 휴리스틱'을 표로 요약하여 실무에서 참고할 수 있도록 정리해보았다.
N-이름
코드 | 내용 |
---|
N1 | 서술적인 이름 사용 |
N2 | 적절한 추상화 수준의 이름 선택 |
N3 | 표준 명명법 사용 |
N4 | 명확한 이름 |
N5 | 범위에 비례하는 길이의 이름 사용 |
N6 | 인코딩 금지 |
N7 | 이름으로 부수 효과 설명 |
N1: 서술적인 이름을 사용하라
- 신중하게 선택한 이름을 보고 모듈 내 함수가 하는 일을 짐작할 수 있어야 한다
N2: 적절한 추상화 수준에서 이름을 선택하라
- 너무 구체적인 구현을 드러내는 이름은 피하라
- 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라
N3: 표준 명명법을 사용하라
- 기존 명명법을 사용하는 이름은 이해하기 더 쉽다
N4: 명확한 이름
- 함수나 변수의 목적을 명확히 밝히는 이름을 선택한다
N5: 긴 범위는 긴 이름을 사용하라
N6: 인코딩을 피하라
- 이름에 유형 및 범위 정보를 넣어서는 안 된다
- 헝가리안 표기법의 오염에서 이름을 보호하라
N7: 이름으로 부수 효과를 설명하라
- 함수, 변수, 클래스가 하는 모든 일을 포함하는 이름을 사용하며 부수효과를 숨기지 않는다
C-주석
코드 | 내용 |
---|
C1 | 부적절한 정보 |
C2 | 쓸모 없는 주석 |
C3 | 중복된 주석 |
C4 | 성의 없는 주석 |
C5 | 주석 처리된 코드 |
C1: 부적절한 정보
- 메타정보 외에 변경 이력이나 날짜 등은 다른 시스템에 저장해도 되므로 주석에 작성하지 않는다
C2: 쓸모 없는 주석
C3: 중복된 주석
- 주석은 코드만으로 다하지 못하는 설명을 부언해야 한다
- 코드로 설명 가능한 내용을 주석으로 반복하면 중복된 주석이다
C4: 성의 없는 주석
- 주절대거나 당연한 소리를 반복하지 말아야 한다
C5: 주석 처리된 코드
- 주석 처리된 코드는 즉각 삭제해야 한다
- git이 기억하는 이상
E-환경
코드 | 내용 |
---|
E1 | 한 단계로 빌드 |
E2 | 한 명령으로 테스트 |
E1: 한 단계로 빌드해야 한다
- 한 명령으로 전체를 체크아웃해 빌드할 수 있어야 한다
E2: 한 명령으로 테스트해야 한다
F-함수
코드 | 내용 |
---|
F1 | 너무 많은 인수 |
F2 | 출력 인수 |
F3 | 플래그 인수 |
F4 | 죽은 함수 |
F1: 너무 많은 인수
- 함수가 받는 인수 개수는 작을수록 좋다
- 아예 없으면 가장 좋다
F2: 출력 인수
- 값 반환을 위해 인수로 전달된 함수를 사용하는 경우 전달된 함수를 출력 인수라고 한다
- 일반적으로 독자는 인수를 출력이 아닌 입력으로 간주하므로 출력 인수는 가독성을 떨어트린다
F3: 플래그 인수
- boolean 인수는 함수가 여러 기능을 수행한다는 명백한 증거로 피해야 한다
F4: 죽은 함수
T-테스트
코드 | 내용 |
---|
T1 | 불충분한 테스트 |
T2 | 커버리지 도구 사용 |
T3 | 사소한 테스트 생략 금지 |
T4 | 무시한 테스트 |
T5 | 경계 조건 테스트 |
T6 | 버그 주변 테스트 |
T7 | 실패 패턴 확인 |
T8 | 테스트 커버리지 패턴 확인 |
T9 | 빠른 테스트 속도 |
T1: 불충분한 테스트
- 테스트는 잠재적으로 깨질 만한 모든 부분을 테스트해야 한다
- 테스트 케이스가 확인하지 않는 조건이 있다면 테스트는 불완전하다
T2: 커버리지 도구를 사용하라
- 커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다
T3: 사소한 테스트를 건너뛰지 마라
- 사소한 테스트가 제공하는 가치는 구현 비용을 넘어선다
T4: 무시한 테스트는 모호함을 뜻한다
- 테스트 케이스를 주석 등으로 처리하였다면 이는 모호함을 나타낸다
T5: 경계 조건을 테스트하라
- 경계 조건은 각별히 신경 서서 테스트 해야한다
T6: 버그 주변은 철저히 테스트하라
T7: 실패 패턴을 살펴라
- 테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다
T8: 테스트 커버리지 패턴을 살펴라
- 통과하는 테스트가 실행한 코드를 살펴보면 실패한 테스트 케이스의 원인이 드러난다
T9: 테스트는 빨라야 한다
G-일반
코드 | 내용 |
---|
G1 | 파일 내 언어 통일 |
G2 | 당연한 동작을 구현하지 않는 경우 |
G3 | 올바른 경계 처리 |
G4 | 안전 절차 무시 |
G5 | 중복 |
G6 | 올바르지 못한 추상화 수준 |
G7 | 기초 클래스가 파생 클래스에 의존 |
G8 | 과도한 정보 |
G9 | 죽은 코드 |
G10 | 수직 분리 |
G11 | 일관성 부족 |
G12 | 잡동사니 |
G13 | 인위적 결합 |
G14 | 기능 욕심 |
G15 | 선택자 인수 |
G16 | 모호한 의도 |
G17 | 잘못 지운 책임 |
G18 | 부적절한 static 함수 |
G19 | 서술적 변수 사용 |
G20 | 이름과 기능이 일치하는 함수 |
G21 | 알고리즘 이해 |
G22 | 논리적 의존성 제거 |
G23 | if, switch 문의 다형성 사용 |
G24 | 표준 표기법 |
G25 | 매직넘버 |
G26 | 정확성 |
G27 | 구조를 통한 강제 |
G28 | 조건의 캡슐화 |
G29 | 부정 조건문 |
G30 | 한 가지 기능만 하는 함수 |
G31 | 숨겨진 시간적인 결합 |
G32 | 일관성 유지 |
G33 | 경계 조건 캡슐화 |
G34 | 한 단계만 내려가는 함수의 추상화 수준 |
G35 | 최상위 정보 설정 |
G36 | 추이적 탐색 |
G1: 한 파일에선 하나의 언어를 사용하라
- 한 소스 파일에 하나의 언어만 사용하는 것이 가장 이상적이다
G2: 당연한 동작을 구현하지 않는다
- 당연한 동작을 구현하지 않으면 코드를 읽는 사람이 기능을 직관적으로 예상하기 어렵게 되면서 저자를 신뢰하지 못하게 된다.
G3: 경계를 올바로 처리하라
- 모든 경계 조건을 찾아내고 테스트하는 테스트 케이스를 작성하라
G4: 안전 절차 무시
- 컴파일러 경고 일부를 꺼버리면 빌드가 쉬워질지도 모르지만 자칫 끝없는 디버깅에 시달리게 될 수 있다
G5: 중복
- 코드에서 중복을 발견할 때마다 추상화할 기히로 간주하라
- 최근 15년 동안 나온 디자인 패턴은 대다수가 중복을 제거하는 방법에 불과하다
G6: 추상화 수준이 올바르지 못하다
- 고차원 개념과 저차원 개념을 섞어서는 안 된다
G7: 기초 클래스가 파생 클래스에 의존
- 클래스를 기초 클래스와 파생 클래스로 구분하는 이유는 고차원 기초 클래스의 개념을 저차원 파생 클래스로부터 분리해 독립성을 보장하기 위해서다
G8: 과도한 정보
- 우수한 개발자는 클래스나 모듈 인터페이스에 노출할 함수를 제한할 줄 알아야 한다
- 함수가 아는 변수 수가 작을수록 좋다. 자료를 숨기고 유틸을 숨겨라. 상수와 변수를 숨겨라.
G9: 죽은 코드
- 실행되지 않는 코드인 죽은 코드는 시간이 지나면 악취를 풍기기 시작한다. 적절한 장례식을 치뤄주자(시스템에서 제거)
G10: 수직 분리
- 변수와 함수는 사용되는 위치에 가깝게 정의한다
G11: 일관성 부족
- 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다
G12: 잡동사니
- 사용하지 않는 변수, 호출하지 않는 함수, 정보를 제공하지 못하는 주석은 모두 잡동사니로 없애야 한다
G13: 인위적 결합
- 서로 무관한 개념을 인위적으로 결합하지 않는다
- 직접적 상호작용이 없는 두 모듈 사이에서 주로 변수, 상수, 함수의 인위적 결합이 일어난다
G14: 기능 욕심
- 클래스 메서드는 다른 클래스의 변수와 함수에 관심을 가져서는 안된다
G15: 선택자 인수
- 선택자 인수는 목적을 기억하기 어렵기 때문에 사용하지 않은 것이 좋다
- boolean 뿐만 아니라 enum, int 등 함수 동작을 제어하려는 인수는 하나 같이 바람직하지 않다
- 인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다
G16: 모호한 의도
G17: 잘못 지운 책임
- 코드는 독자가 자연스럽게 기대할 위치에 배치해야 한다
- 때로는 개발자가 독자에게 직관적인 위치가 아니라 개발자에게 편한 위치에 배치한다
G18: 부적절한 static 함수
- 재정의(override) 가능성이 조금이라도 있다면 static 함수가 아닌 인스턴스 함수로 정의한다
G19: 서술적 변수
- 가독성을 높이는 가장 효과적인 방법 중 하나가 계산을 여러 단계로 나누고 중간 값으로 서술적인 변수 이름을 사용하는 것이다
- 서술적인 변수 이름은 많으면 많을수록 좋다
G20: 이름과 기능이 일치하는 함수
- 이름만으로 분명하지 않아 구현을 살펴야 한다면 이름을 바꾸거나 기능을 정리해야 한다
G21: 알고리즘을 이해하라
- 구현이 끝났다고 선언하기 전에 함수가 돌아가는 방식을 확실히 이해하는지 확인하라
- 모든 테스트 케이스 통과만으로는 부족하며 작성자가 알고리즘이 올바르다는 사실을 알아야 한다
G22: 논리적 의존성을 물리적으로 드러내라
- 논리적 의존성을 가진 가정을 명시적으로 요청(물리적)하는 편이 좋다
G23: if/else 혹은 switch/case 문보다 다형성을 사용하라
- 같은 선택을 수행하는 여러 코드에서 다형성 객체를 생성해 하나의 switch문만 사용한다
G24: 표준 표기법을 따르라
- 팀은 업계 표준에 기반한 구현 표준을 팀원 모두가 따라야 한다
G25: 매직넘버는 명명된 상수로 교체하라
- 코드에서 숫자를 바로 사용하지 말고 명명된 상수 뒤로 숨겨라
G26: 정확하라
- 코드에서 무언가를 결정할 때는 정확히 결정한다
- 결정을 내리는 이유와 예외를 처리할 방법을 분명히 알아야 한다
G27: 관례보다 구조를 사용하라
- 설계 결정을 강제할 때는 명명 관례보다는 구조 자체로 강제하는 것이 좋다
G28: 조건을 캡슐화하라
- 조건문의 의도를 분명히 밝히는 함수 등으로 표현하라
G29: 부정 조건은 피하라
- 부정 조건은 긍정 조건보다 이해하기 어려우므로 가능하면 긍정 조건으로 표현한다
G30: 함수는 한 가지만 해야 한다
- 함수를 짜다 보면 한 함수 안에 여러 단락을 이어 일련의 작업을 수행하고픈 유혹에 빠진다
- 이런 함수는 한 가지만 수행하는 좀 더 작은 함수 여럿으로 나누어야 한다
G31: 숨겨진 시간적인 결합
- 시간적 결합은 때로 필요하지만 숨겨서는 안 된다
- 의도적으로 추가한 구문의 복잡성이 원래 있던 시간적 복잡성을 드러내는 경우가 존재한다
G32: 일관성을 유지하라
- 코드 구조를 잡을 때는 이유를 고민하고 그 이유를 코드 구조로 일관성 있게 명백히 표현하라
G33: 경계 조건을 캡슐화하라
- 경계 조건은 변수로 캡슐화하여 한 곳에서 별도로 처리해야 빼먹거나 놓치지 않는다
G34: 함수는 추상화 수준을 한 단계만 내려가야 한다
- 함수 내 모든 문장은 추상화 수준이 동일해야 한다
- 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다
- 추상화 수준 분리는 리팩터링의 가장 중요한 수행 이유 중 하나이다
G35: 설정 정보는 최상위 단계에 둬라
- 설정 관련 상수는 최상위 단계에 두어야 변경하기 쉽다
- 저차원 함수에 상수 값을 정의하면 안 된다
G36: 추이적 탐색을 피하라
- 일반적으로 한 모듈은 주변 모듈을 모를수록 좋다(Law of Demeter)
- 내가 사용하는 모듈이 내게 필요한 서비스를 모두 제공해야 한다
- 원하는 메서드를 찾느라 객체 그래프를 따라 시스템을 탐색할 필요가 없어야 한다