Chapter 17
17 냄새와 휴리스틱 Smells and Heuristics
주석
C1: 부적절한 정보
변경이력은 소스를 번잡하게 만듬.
주석은 코드와 설계에 기술적인 설명만.
C2: 쓸모없는 주석
오래되서 쓸모없는 주석은 즉시 삭제.
C3: 중복된 주석
구구절절 설명하는 중복된 주석
C4: 성의없는 주석
간결하고 명표하게 작성.
C5: 주석처리된 코드
소스코드 관리 시스템이 기억하기 때문에
쓸모없는 코드는 주석처리보다 지우자.
환경
E1: 여러단계로 빌드해야 한다.
빌드는 간단히 한 단계로, 한 명령으로 빌드할 수 있어야 한다.
E2: 여러단계로 테스트해야 한다
모든 단위 테스트는 한 명령으로 돌려야 함.
함수
F1: 너무 많은 인수
함수의 인수 개수는 작을수록, 아예 없으면 더 좋음.
F2: 출력 인수
함수에서 어떤 상태를 변경해야 하면, 함수가 속한 객체의 상태를 변경.
F3: 플래그 인수
boolean 인수는 함수가 여러 기능을 수행한다는 증거. 피해야 함.
F4: 죽은 함수
아무도 호출하지 않는 함수는 삭제. 죽은 코드는 낭비.
일반
G1: 한 소스파이에 여러 언어를 사용한다.
한 소스파일 내에서 언어 수와 범위를 최대한 줄이자.
G2: 당연한 동작을 구현하지 않는다
한수나 클래스는 장연히 여길 만한 동작과 기능을 제공해야 함 - the principle of surprise
G3: 경계를 올바로 처리하지 않는다
스스로의 직관에 의존하지 말고, 모든 경계조건을 찾아내고 테스트하라.
G4: 안전 절차 무시
컴파일러 경고를 무시하지 말라
G5: 중복
가장 중요한 규칙.
코드에서 중복을 발견할 때마다 추상화할 기회로 간주하자.
-> 중복된 코드를 하위 루틴이나 다른 클래스로 분리.
- 똑같은 코드가 여러번 나오는 중복
-> 간단한 함수로 교체 - 조건문으로 조건 거듭 확인하는 중복
-> 다형성으로 대체 - 알고리즘이 유사하나 코드가 서로 다른 중복
-> Template Method 패턴 이나 Strategy 패턴으로 중보으르 제거.
디자인 패턴 대다수 중복을 제거하는 잘 알려진 방법. 적극 활용하자.
G6: 추상화 수준이 올바르지 못하다
저차원 상세 개념에서 고차원 일반 개념을 분리한다.
모든 저차원 개념은 파생 클래스에 넣고, 모든 고차원 개념은 기초 클래스에 넣음.
기초 클래스는 구현정보에 무지해야 함.
G7: 기초 클래스가 파생 클래스에 의존한다
기초클래스가 파새 클래스 개념으로부터 분리 독립성을 유지 해야함.
일반적으로 기초 클래스와 파생 클래스를 다른 jar 파일로 배포하는 편이 좋음.
독립적인 개별 컴포넌트 단위로 시스템 배치 가능.
G8: 과도한 정보
잘 정의된 인터페잇는 많은 함수를 제공하지 않고 모듈 단위가 작아 결합도가 낮음.
G9: 죽은 코드
실행되지 않는 코드, 불가능한 조건을 확인하는 if문, 호출하지않는 유틸 함수 등.
G10: 수직 분리
변수와 함수는 사용되는 위치에 가깝게 정의
비공개 함수는 처음 호출한 직후에 정의.
G11: 일관성 부족
어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현.
일관성 있게 동일한 변수 이름 사용하기.
G12: 잡동사니
쓸데없는 코드 정리하기.
G13: 인위적 결합
서로 무관한 개념을 인위적으로 결합하지 않기.
enum, 범용 static 함수가 특정 클래스에 소속되는 것.
함수, 상수, 변수 선언시 올바른 위치를 고민하자.
G14: 기능 욕심
메서드가 다른 객체의 참조자와 변경자를 사용해 그 객체 내용을 조작하는 것은 지양.
G15: 선택자 인수
선택자 인수는 큰 함수를 작은 함수로 쪼개지 않으려는 게으름.
G16: 모호한 의도
코드의 의도를 명확히 표현하도록해 가독성을 높임.
G17: 잘못 지운 책임
코드를 배치하는 위치도 중요.
G18: 부적절한 static 함수
함수를 재정의할 가능성이 있는 함수는 static으로 정의하지 않는것이 좋음.
G19: 서술적 변수
가독성을 높이기 위해 계산을 여러 단계로 나누고 중간 값으로 서술적인 변수 이름을 사용하는 방법.
G20: 이름과 기능이 일치하는 변수
이름으로 기능이 분명하지 않으면 좋은 이름으로 바꾸자
G21: 알고리즘을 이해하라
작성자가 알고리즘을 명확하게 파악하는것이 중요.
코드가 잘 돌아가는 것과 그 알고리즘이 올바르다는 것은 다름.
G22: 논리적 의존성을 물리적으로 드러내라
의존하는 모든 정보를 명시적으로 요청하는게 좋음.
G23: If/Else 혹은 Switch 문보다 다형성을 사용하라
switch를 사용하기전에 다형성을 먼저 고려하라
유형보다 함수가 더 쉽게 변하느 경우가 드무므로.
-> 선택 유형 하나에는 switch문 한번만 사용.
같은 선택을 수행하는 다른코드에서 다형성 객첼르 생성해 switch문 대신함.
G24: 표준 표기법을 따르라
업계 표준에 기반한 구현 표준을 따르자.
G25: 매직 숫자는 명명된 상수로 교체하라
숫자는 명명된 상수 뒤로 숨기자.
코드가 자명하고 고유한 숫자라면 상수뒤로 숨길 필요 없음.
G26: 정확하라
부동소수점으로 통화를 표현하는 행동은 범죄에 가까움.
가능성이 희박하다고 잠금과 트랜잭션 관리를 건너뛰는 행동은 게으름.
통화를 다룬다면(Money.class) 정수를 사용하고 반올림으로 올바로 처리.
병행특성으로 동시에 갱신할 가능성이 있다면 잠금 매커니즘을 구현해야함.
G27: 관례보다 구조를 사용하라
설계 결정을 강제할 때는 규칙보다 관례를 사용.
G28: 조건을 캡슐화하라
부울 논지 조건 보다는 조검의 의도를 분명히 밝히는 함수로 표현하자.
if(shouldBeDeleted(timer))
아래의 코드가 더 좋음
if(timer.hasExpired() && !timer.isRecurrent())
G29: 부정 조건은 피하라
가능하면 긍정조건으로 표현하자
if(!buffer.shouldNotCompact())
아래의 코드가 더 좋음
if(buffer.shouldCompact())
G30: 함수는 한 가지만 해야 한다
한가지만 수행하는 작은 함수 여럿으로 나눠야 함.
G31: 숨겨진 시간적인 결합
시간 결합이 필요할때는 숨기면 안됨.
함수의 인수를 적절히 배치해 함수가 호출되는 순서를 명백히 해야함.
G32: 일관성을 유지하라
구조에 일관성이 필요.
G33: 경계 조건을 캡슐화하라
경계 조건은 한 곳에서 별도로 처리함.
G34: 함수는 추상화 수준을 한 단계만 내려가야 한다.
함수 내 문장은 추상화 수준이 동일해야 함.
추상화 수준 분리는 리팩터링을 수행하는 중요한 이유.
G35: 설정 정보는 최상위 단계에 둬라
기본 상수나 설정 관련 상수는 숨기지 말고 추상화 최상위 단계에 둬야 함.
G36: 추이적 탐색을 피하라
디미터의 법칙 law of demeter - 자신이 직접 사용하는 모듈만 알아야 함.
a.getB().getC()
나중에 B와 C 사이 Q를 넣기 쉽지 않고 아키텍처가 굳어짐.
다음과 같이 간단한 코드
myCollaborator.doSomething();
자바
J1: 긴 import 목록을 피하고 와일드카드를 사용하라.
import package.*;
사용하는 패키지를 간단히 명시하면 충분.
와일드 카드로 패키지를 지정하면 특정 클래스가 존재할 필요가 없으므로, 의존성이 생기지 않고, 모듈간 결합성이 낮아짐.
J2: 상수는 상속하지 않는다
상수를 상속하는 인터페이스에 넣는 행위는 범위규칙을 속이는 것.
대신 static import 를 사용하자.
J3: 상수 대 Enum
enum은 이름이 부여된 열거체. 메서드와 필드 사용가능.
이름
N1: 서술적인 이름을 사용하라
가독성의 90%는 이름이 결정함.
N2: 적절한 추상화 수준에서 이름을 선택하라
구현을 드러내는 이름보다 작업대상 클래스, 함수 위치 추상화 수준을 반영하는 이름을 선택.
N3: 가능하다면 표준 명명법을 사용하라
프로젝트에 유효한 의미가 담긴 이름을 사용하면 코드 이해가 쉬워짐.
decorator 패편을 활용한다면 클래스 이름에 넣어줌.
N4: 명확한 이름
함수나 변수의 목적을 명확히 하자.
N5: 긴 범위는 긴 이름을 사용하라
간단한 for문 처럼 범위가 작으면 간단한 변수 사용 가능할수 있으나 범위가 크다면 명확한 이름이 필요.
N6: 인코딩을 피하라
이름에 유형정보나 범위점보는 불필요.
이름앞에 접두어는 필요없음.
N7: 이름으로 부수 효과를 설명하라
하는일을 모두 기술하는 이름을 사용.
테스트
T1: 불충분한 테스트
잠재적으로 깨질만한 부분을 모두 테스트하자.
T2: 커버리지 도구를 사용하라!
T3: 사소한 테스트를 건너뛰지 마라
T4: 무시한 테스트는 모호함을 뜻한다
불분명한 요구사항은 테스트 케이스를 주석으로 처리하거나 테스트 케이스에 @Ignore를 붙여 표현.
T5: 경계 조건을 테스트하라
T6: 버그 주변은 철저히 테스트하라
T7: 실패 패턴을 살펴라
실패 패턴으로 문제를 진단 할 수 있음.
T8: 테스트 커버리지 패턴을 살펴라
통과하는 테스트가 실행하거나 실행하지 않는 코드 패턴.
T9: 테스트는 빨라야 한다
느리면 건너뛰게 됨.
결론
완전한 목록과 목표를 달성했다고, 규칙만 따른다고 완벽한 코드를 작성할 수 없다.
전문가 정신과 장인정신은 가치에서 나오며 그 가치에 기반한 규율과 절제가 필요하다.
참고문헌
[Refactoring]: Refactoring: Improving the Design of Existing Code, Martin Fowler et al., Addison-Wesley, 1999.
[PRAG]: The Pragmatic Programmer, Andrew Hunt, Dave Thomas, Addison-Wesley, 2000.
[GOF]: Design Patterns: Elements of Reusable Object Oriented Software, Gamma et al., Addison-Wesley, 1996.
[Beck97]: Smalltalk Best Practice Patterns, Kent Beck, Prentice Hall, 1997.
[Beck07]: Implementation Patterns, Kent Beck, Addison-Wesley, 2008.
[PPP]: Agile Software Development: Principles, Patterns, and Practices, Robert C. Martin, Prentice Hall, 2002.
[DDD]: Domain Driven Design, Eric Evans, Addison-Wesley, 2003.