[CleanCode] 17장 냄새와 휴리스틱
주석
- C1. 부적절한 정보 피하기
- 다른 시스템에 저장할 정보는 주석으로 적절하지 못한다.
- 일반적으로 작성자, 최종 수정일, SPR 번호 등과 같은 메타 정보만 주석으로 넣고, 추가적으로 코드와 설계에 기술적인 설명을 하는 수단으로 써야 한다.
- C2. 쓸모없는 주석 피하기
- 오래된 주석, 엉뚱한 주석, 잘못된 주석은 더 이상 쓸모가 없다. 쓸모없는 주석은 없애야 한다.
- C3. 중복된 주석 피하기
- 코드만으로 충분한데 구구절절 설명하는 주석이 중복된 주석은 좋지 않다.
- C4. 성의 없는 주석 피하기
- 단어를 신중하게 선택하고, 간결하고 명료하게 작성해야 한다.
- C5. 주석 처리된 코드 피하기
- 주석으로 처리된 코드는 남들이 읽을 때 매우 눈에 거슬리고 매일매일 낡아간다.
- 이는 해당 모듈을 오염시키고, 읽는 사람을 헷갈리게 만든다.
환경
- E1. 한 단계로 빌드하기
- 빌드는 간단히 한 단계로 끝나야 한다.
- E2. 한 단계로 테스트하기
- 모든 단위 테스트는 한 명령으로 돌려야 한다.
- 이 방법이 빠르고 쉽고 명백하며, 가장 중요하다.
함수
- F1. 너무 많은 인수 피하기
- 인수는 작으면 작을수록 좋다.
- F2. 출력 인수 피하기
- 출력이 인수는 직관을 정면으로 위배하므로 안 쓰는 것이 좋다.
- F3. 플래그 인수 피하기
- Boolean 인수는 함수가 여러 기능을 수행한다는 증거이다.
- 플래그 인수는 혼란을 피하기 위해서 피해야 한다.
- F4. 죽은 함수 피하기
- 아무도 호출하지 않는 함수는 삭제한다.
일반
- G1. 한 소스 파일에 여러 언어를 사용을 줄이기
- 이상적으로는 소스 파일 하나에 언어 하나만 사용하는 방식이 좋다.
- 현실적으로는 이가 어렵기 때문에 소스 파일에서 언어 수와 범위를 줄이는 방법이 중요하다.
- G2. 당연한 동작을 구현하기
- 최소 놀람의 원칙을 지켜서 함수나 클래스는 프로그래머가 당연하게 여길 만한 동작과 기능을 제공해야 한다.
- G3. 경계를 올바르게 처리하기
- 코드는 올바르게 동작해야 한다.
- 모든 경계 조건, 구석진 곳, 예외 등을 신경을 써야 하며 모든 경계 조건을 테스트하는 케이스가 있어야 한다.
- G4. 안전 절차를 지키기
- 안전 절차를 무시하면 위험하다.
- 예를 들면 변수를 직접 제어하거나 컴파일러 경고를 꺼버리면 끊임없는 문제가 발생한다.
- G5. 중복을 피하기
- 단 한 번만 사용하는 규칙을 지켜야 한다.
- 코드에서 중복을 발견할 때마다 이를 추상화시키고, 중복을 다른 클래스로 분리해야 한다.
- G6. 추상화 수준이 올바르게 지키기
- 추상화는 저 차원 상세 개념을 고차원 일반 개념으로 분리한다.
- 고차원 개념을 표현하는 추상(기초) 클래스와 저차원 개념을 표현하는 파생 클래스를 통해서 추상화를 진행해야 한다.
- G7. 기초 클래스는 파생 클래스에 독립적으로
- 기초 클래스와 파생 클래스로 나누는 큰 이유는 하나는 개념을 분리해 독립성을 보장하기 위해서이다.
- 따라서 서로 독립적으로 진행해야 하고, 이는 이후에 유지보수에 큰 장점을 가진다.
- G8. 과도한 정보를 피하기
- 잘 정의된 모듈은 인터페이스가 아주 작다.
- 많은 함수를 제공하지 않기 때문에 결합도가 낮다.
- 이런 식으로 인터페이스에 노출할 함수를 제한해야 하고, 클래스가 제공하는 메서드 수는 작을수록 좋다.
- 마찬가지로 변수가 작을 수록 좋다.
- 더 나아가서 자료를 숨기는 것 또한 중요하다.
- G9. 죽은 코드를 피하기
- 실행되지 않는 코드는 시스템에서 제거해주어야 한다.
- G10. 수직 분리 줄이기
- 변수와 함수는 사용되는 위치에 가깝게 정의해야 한다.
- 비공개 함수는 처음으로 호출한 직후에 정의해야 한다.
- G11. 일관성 유지
- 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현해야 한다.
- 이러한 일관성을 유지한다면 코드를 읽고 수정하기가 쉬워진다.
- G12. 잡동사니 없애기
- 비어있는 기본 생성자와 같은 잡동사니는 삭제한다.
- G13. 인위적인 결합 피하기
- 서로 무관한 개념은 인위적으로 결합하지 않는다.
- G14. 기능 욕심내지 말기
- 기능을 위해서 다른 클래스의 변수와 함수에 관심을 가져서는 안 된다.
- 어쩔 수 없는 경우를 제외하고는 최대한 피해야 한다.
- G15. 선택자 인수 피하기
- 선택자 인수는 큰 함수를 작은 함수로 쪼개지 않으려는 게으름의 결과이다.
- G16. 분명한 의도 사용
- 코드를 짤 때는 의도를 최대한으로 밝혀야 한다.
- G17. 명백한 위치 선정
- 코드는 독자가 자연스럽게 기대할 위치에 배치해야 한다
- G18. 부적절한 static 함수 피하기
- 일반적으로 static 함수보다는 인스턴스 함수가 더 좋고, 조금이라도 의심스럽다면 인스턴스 함수를 사용하는 방법이 더 좋다.
- 반드시 static 함수로 정의해야 한다면 재정의 가능성이 없는지 꼼꼼히 고려해야 한다.
- G19. 서술적인 변수 사용하기
- 서술적인 변수 이름 등인 일반적으로 더 많을수록 좋다.
- 좋은 변수 이름만 붙여도 모듈이 읽기 쉬운 모듈로 탈바꿈한다.
- G20. 이름과 기능이 일치하는 함수
- 이름만으로 분명하지 않아, 구현을 봐야 한다면 더 좋은 이름을 선정해야 한다.
- G21. 알고리즘 이해하기
- 기능이 뻔히 보일 정도로 함수를 깔끔하게 구해야 하며, 이를 통해 알고리즘이 올바르다는 것을 보여주어야 한다.
- G22. 논리적 의존성은 물리적으로도 나타내기
- 한 모듈이 다른 모듈에 의존한다면 물리적인 의존성도 주어야 한다.
- G23. If/Else 혹은 Switch/Case 문보다 다형성을 사용하기
- 대부분의 개발자가 switch 문을 사용하는 이유는 그 상황에서 가장 올바른 선택보다는 손쉬운 선택이므로 선택한다.
- 유형보다 함수가 더 쉽게 변하는 경우는 드물기 때문에, 같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성하고, 선택 유형 하나에는 switch 문을 한 번만 사용해야 한다.
- G24. 표준 표기법을 사용하기
- 팀은 업계 표준에 기반한 구현 표준을 지켜야 한다.
- G25. 매직 숫자는 명명된 상수로 교체하기
- 예를 들면 86400이라는 숫자는 SECONDS_PER_DAY와 같이 표현해야 한다.
- G26. 정확하게 작성하기
- 코드에서 뭔가를 결정할 때는 정확히 결정해야 한다.
- 결정을 내리는 이유와 예외를 처리할 방법을 정확하게 알아야 한다.
- G27. 관례보다는 구조를 사용하기
- 명명 관례도 좋지만 구조 자체로 강제로 하는 경우가 더 좋을 수 있다.
- G28. 조건을 캡슐화하기
- 부울 논리를 넣는 것보다 의도를 분명히 밝히는 함수로 표현을 하는 것이 더 좋다.
- 즉, if(shouldBeDeleted(timer)) 가 if(timer.hasExpired() &&! timer.isRecurrent()) 보다 좋다.
- G29. 부정 조건은 피하기
- 부정 조건은 긍정 조건보다 이해하기 어렵다.
- 가능하다면 긍정 조건을 사용하는 것이 좋다.
- G30. 함수는 한 가지만 하기
- G31. 숨겨진 시간적인 결합 피하기
- 때로는 시간적인 결합이 필요하다.
- 단 이때는 시간적인 결합을 숨겨서는 안 된다.
- 실행되는 순서가 중요하기 때문에 일종의 연결 소자를 통해 시간적인 결합을 노출하는 것도 좋은 방법이 된다.
- G32. 일관성 유지하기
- 코드 구조를 잡을 때는 이유를 고민하고, 그 이유를 코드 구조로 명백하게 표현해야 한다.
- G33. 경계 조건을 캡슐화 하기
- 경계 조건은 빼먹기나 놓치기 십상이다.
- G34. 함수는 추상화 수준을 한 단계만 내려가야 한다.
- 함수 내 모든 문장은 추상화 수준이 동일해야 한다.
- 그리고 그 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계 낮아야 한다.
- G35. 설정 정보는 최상위 단계에 두기
- 추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저 차원 함수에 숨겨서는 안 된다.
- 설정 관련 상수는 최상위 단계에 두어야 한다.
- 그래야 변경하기 쉽고, 인수로 넘길 수 있다.
- G36. 추이적 탐색 피하기
- 일반적으로 한 모듈은 주변 모듈을 모르면 모를수록 좋다.
- 이를 디미터의 법칙(Law of Demeter)라고 부른다.
자바
- J1. 긴 import 목록을 피하기
- 와일드카드를 사용하는 것이 좋다.
- 패키지에서 클래스를 둘 이상 사용한다면 와일드카드를 사용해 패키지 전체를 가져오는 것이 좋다 Ex) import package.*;
- J2. 상수는 상속하지 않는다.
- J3. 상수 VS Enum
- 자바 5부터는 enum을 제공하기 때문에 enum을 사용하는 방법은 좋다.
- enum은 이름이 부여된 열거체에 속하며, 메서드와 필도도 사용할 수 있기 때문에 int보다 훨씬 더 유연하고 서술적인 강력한 도구이다.
이름
- N1. 서술적인 이름 사용하기
- 서술적인 이름을 신중하게 고른다.
- 소프트웨어 가독성의 90%는 이름이 결정한다.
- N2. 적절한 추상화 수준에서 이름 선택하기
- 구현을 드러내는 이름은 피하기
- 적절한 추상화 수준에서 이름을 선택하는 다른 연결방식에도 사용 가능하다.
- N3. 가능하다면 표준 명명법 사용하기
- N4. 명확한 이름
- 함수나 변수의 목적을 명확히 밝히는 이름을 선택하기
- N5. 긴 범위는 긴 이름을 사용하기
- 이름 길이는 범위 길이에 비례해야 한다.
- N6. 인코딩을 피하기
- 이름에 유형 정보나 범위 정보를 넣어서는 안 된다.
- N7. 이름으로 부수 효과를 설명하기
- 함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용하기
- 이름에 부수 효과를 숨기지 않기
테스트
- T1. 불충분한 테스트
- 테스트 케이스는 잠재적으로 깨질 만한 부분을 모두 테스트해야 한다.
- 테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불안정한 테스트이다
- T2. 커버리지 도구를 사용하기
- 커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다.
- 이를 사용하면 테스트가 불충분한 모듈, 클래스, 함수를 찾기가 쉬워진다.
- T3. 사소한 테스트 건너뛰지 말기
- 사소한 테스트는 짜기 쉽다.
- 사소한 테스트가 제공하는 문서적 가치는 구현에 드는 비용을 넘어선다.
- T4. 무시한 테스트는 모호함을 뜻한다.
- 불분명한 요구사항은 테스트 케이스를 주석으로 처리하고나, 테스트 케이스에 @I- gnore를 붙여서 표현해버린다.
- T5. 경계 조건을 테스트하기
- 경계조건은 각별히 신경 써서 테스트하기
- T6. 버그 주변은 철저히 테스트하기
- 버그는 서로 모이는 경향이 있다.
- T7. 실패 패턴을 살펴보기
- 테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다.
- 테스트 케이스를 꼼꼼하게 짜게 되면 실패와 성공 패턴만 봐도 답을 찾을 수도 있다.
- T8. 테스트 커버리지 패턴을 살피기
- 통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러난다.
- T9 테스트는 빨라야 한다
- 느린 테스트 케이스는 실행하지 않게 된다.
참고 링크