이 장의 주제
- 마틴 파울러의 『Refactoring』에서 거론된 다양한 ‘코드 냄새’와 저자가 추가한 냄새들의 목록이다.
- 저자가 코드를 짜면서 사용하는 기교와 휴리스틱도 포함한다.
- 해당 목록은 다양한 프로그램을 검토하고 리팩터링하면서 만들었다.
결론
- 이 장에서 소개하는 휴리스틱과 냄새 목록이 완전하다 말하기는 어렵다.
- 여기서 소개한 목록은 가치 체계를 피력할 뿐이다.
- 사실상 가치 체계는 이 책의 주제이자 목표다.
- 전문가 정신과 장인 정신은 가치에서 나온다. 그 가치에 기반한 규율과 절제가 필요하다
코드 냄새
주석
C1: 부적절한 정보
- 다른 시스템에 저장할 정보는 주석으로 적절하지 못하다.
- 시스템의 종류: 소스 코드 관리 시스템, 버그 추적 시스템, 이슈 추적 시스템, 기타 기록 관리 시스템 등
- 예를 들어, 변경 이력은 장황한 날자와 따분한 내용으로 소스 코드만 번잡하게 만든다.
- 일반적으로 작성자, 최종 수정일, SPR(Software Problem Report) 번호 등과 같은 메타 정보만 주석으로 넣는다.
- 주석은 코드와 설계에 기술적인 설명을 부연하는 수단이다.
C2: 쓸모 없는 주석
- 오래된 주석, 엉뚱한 주석, 잘못된 주석은 더 이상 쓸모가 없다.
- 쓸모 없어질 주석은 아예 달지 않는 편이 가장 좋다.
- 쓸모 없어진 주석은 코드와 무관하게 혼자서 따로 놀며 코드를 그릇된 방향으로 이끄므로, 재빨리 삭제하는 편이 가장 좋다.
C3: 중복된 주석
- 코드만으로 충분한데 구구절절 설명하는 주석을 말한다.
- 주석은 코드만으로 다하지 못하는 설명을 부언한다.
C4: 성의 없는 주석
- 주석을 달 참이라면 시간을 들여 최대한 멋지게 작성해야 한다.
- 시간을 들여 최대한 멋지게 작성해야 한다.
- 단어를 신중히 선택해야 한다.
- 문법과 구두점을 올바로 사용해야 한다.
- 주절대거나 당연한 소리를 반복하지 않아야 한다.
- 간결하고 명료하게 작성해야 한다.
C5: 주석 처리된 코드
- 주석으로 처리된 코드를 발견하면 즉각 지워버려라.
- 주석으로 처리된 코드는 흉물 그 자체다.
- 더 이상 존재하지 않는 함수를 호출
- 이름이 바뀐 변수를 사용
- 더 이상 사용하지 않는 표기법을 따름
- 자신이 포함된 모듈을 오염시키고 읽는 사람을 헷갈리게 만든다.
환경
E1: 여러 단계로 빌드해야 한다.
- 빌드는 간단히 한 단계로 끝나야 한다.
- 소스 코드 관리 시스템에서 이것저것 따로 체크아웃할 필요가 없어야 한다.
- 불가해한 명령이나 스크립트를 잇달아 실행해 각 요소를 따로 빌드할 필요가 없어야 한다.
- 온갖 JAR 파일, XML 파일, 기타 시스템에 필요한 파일을 찾느라 여기저기 뒤적일 필요가 없어야 한다.
- 한 명령으로 전체를 체크아웃해서 한 명령으로 빌드할 수 있어야 한다.
svn get mySystem
cd mySystem
ant all
E2: 여러 단계로 테스트 해야 한다.
- 모든 단위 테스트는 한 명령으로 돌려야 한다.
- IDE 에서 버튼 하나로 모든 테스트를 돌리는 것이 가장 이상적
- 아무리 열악한 환경이라도 셸에서 명령 하나로 가능해야 한다.
- 모든 테스트를 한 번에 실행하는 능력은 아주 근본적이고 아주 중요하다.
- 따라서 그 방법이 빠르고, 쉽고, 명백해야 한다.
함수
F1: 너무 많은 인수
- 함수에서 인수 개수는 작을 수록 좋다.
- 0개 > 1개 > 2개 > 3개 순으로 차례로 좋다. (앞선 것이 더 좋은 것)
- 넷 이상은 그 가치가 아주 의심스러우므로 최대한 피한다.
F2: 출력 인수
- 출력 인수는 직관을 정면으로 위배한다.
- 함수에서 뭔가의 상태를 변경해야 한다면 출력 인수를 쓰는 대신 함수가 속한 객체의 상태를 변경한다.
F3: 플래그 인수
- boolean 인수는 함수가 여러 기능을 수행해야 한다는 명백한 증거다.
- 플래그 인수는 혼란을 초래하므로 피해야 마땅하다.
F4: 죽은 함수
- 아무도 호출하지 않은 함수는 삭제한다.
- 죽은 함수는 낭비이므로 과감히 삭제하라.
일반
G1: 한 소스 파일에 여러 언어를 사용한다.
- 오늘날 프로그래밍 환경은 한 소스 파일 내에서 다양한 언어를 지원한다.
- 예를 들어, 어떤 자바 소스 파일은 XML, HTML, YAML, Javadoc, JaavScript, 영어 등을 포함한다.
- 이상적으로는 소스 파일 하나에 언어 하나만 사용하는 방식이 가장 좋다.
- 현실적으로는 여러 언어가 불가피하다.
- 각별한 노력을 기울여 소스 파일에서 언어 수와 범위를 최대한 줄이도록 애써야 한다.
G2: 당연한 동작을 구현하지 않는다.
- 최소 놀람의 원칙에 의거해 함수나 클래스는 다른 프로그래머가 당연하게 여길 만한 동작과 기능을 제공해야 한다.
💡 최소 놀람의 원칙 (The Principle of Least surprise)
필요한 기능에 크나큰 깜짝 놀래킬만한 요소가 있다면 해당 기능을 다시 설계할 필요가 있을 수 있다.
더 일반적으로 이야기하면 이 원칙은 시스템의 구성 요소가 대부분의 사용자들이 행동할 것으로 예측되는 방식으로 동작하는 것이 좋다는 것을 의미한다.
G3: 경계를 올바로 처리하지 않는다.
- 코드는 올바로 동작해야 한다.
- 모든 경계 조건을 찾아내고, 모든 경계 조건을 테스트하는 테스트 케이스를 작성하라.
- 모든 경계 조건, 모든 구석진 곳, 모든 기별, 모든 예외는 우아하고 직관적인 알고리즘을 좌초시킬 암초다.
G4: 안전 절차 무시
- 안전 절차를 무시하면 위험하다.
serialVersionUID
를 직접 제어할 필요가 있을지도 모르지만 그대로 직접 제어는 언제나 위험하다.
- 컴파일러 경고 일부를(혹은 전부를) 꺼버리면 빌드가 쉬워질지 모르지만 자칫하면 끝없는 디버깅에 시달린다.
- 실패하는 테스트를 제껴두고 나중으로 미루는 태도는 위험하다.
G5: 중복
이 책에 나오는 가장 중요한 규칙 중 하나
- 소프트웨어 설계를 거론하는 저자라면 거의 모두가 이 규칙을 언급한다.
- 데이비트 토머스와 앤디 헌트는 이를 DRY(Don’t Repeat Yourself) 원칙이라 부른다.
- 켄트 백은 익스트림 프로그래밍의 핵심 규칙 중 하나로 선언한 후 “한 번, 단 한 번만(Once, and only once)”이라 명명했다.
- 론 제프리스는 이 규칙을 “모든 테스트를 통과한다”는 규칙 다음으로 중요하게 꼽았다.
- 코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라.
- 중복된 코드를 하위 루틴이나 다른 클래스로 분리
- 이렇듯 추상화로 중복을 정리하면 설계 언어의 어휘가 늘어난다.
- 추상화 수준을 높였으므로 구현이 빨라지고 오류가 적어진다.
- 가장 뻔한 유형은 똑같은 코드가 여러 차례 나오는 중복이다.
- 좀 더 미묘한 유형은 여러 모듈에서 일련의 switch/case나 if/else 문으로 똑같은 조건을 거듭 확인하는 중복이다.
- 더더욱 미묘한 유형은 알고리즘이 유사하나 코드가 서로 다른 중복이다.
- Template Method 패턴이나 Strategy 패턴으로 중복을 제거한다.
- 사실 최근 15년 동안 나온 디자인 패턴은 대다수가 중복을 제거하는 잘 알려진 방법에 불과하다.
- BCNF(Boyce-Codd Normal Form): 데이터베이스 스키마에서 중복을 제거하는 전략
- OO: 모듈을 정리하고 중복을 제거하는 전략
G6: 추상화 수준이 올바르지 못하다.
- 추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다.
- 때로 우리는 고차원 개념을 표현하는 추상 클래스와 저차원 개념을 표현하는 파생 클래스를 생성해 추상화를 수행
- 때로는 소스 파일과 모듈과 컴포넌트로 분리
- 추상화로 개념을 분리할 때는 철저해야 한다.
- 모든 저차원 개념은 파생 클래스에 넣는다.
- 모든 고차원 개념은 기초 클래스에 넣는다.
- 기초 클래스는 구현 정보에 무지해야 마땅하다.
- ex) 세부 구현과 관련한 상수, 변수, 유틸리티 함수는 기초 클래스에 넣으면 안 된다.
- 잘못된 추상화 수준은 거짓말이나 꼼수로 해결하지 못한다. → 임시변통으로 고치기는 불가능
G7: 기초 클래스가 파생 클래스에 의존한다.
- 개념을 기초 클래스와 파생 클래스로 나누는 가장 흔한 이유는 고차원 기초 클래스 개념을 저차원 파생 클래스 개념으로부터 분리해 독립성을 보장하기 위해서다.
- 기초 클래스는 파생 클래스를 아예 몰라야 마땅하다.
- 기초 클래스가 파생 클래스를 사용한다면 뭔가 문제가 있다는 말이다.
- 예외로, 간혹 파생 클래스 개수가 확실히 고정되었다면 기초 클래스에 파생 클래스를 선택하는 코드가 들어간다.
- FSM(Finite State Machine) 구현에서 많이 본 사례
- FSM은 기초 클래스와 파생 클래스가 굉장히 밀접하며 언제나 같은 JAR 파일로 배포한다.
- 일반적으로는 기초 클래스와 파생 클래스를 다른 JAR로 배포하는 편이 좋다.
- 독립적인 개별 컴포넌트 단위로 시스템을 배치할 수 있어서, 만약 컴포넌트를 변경한다면 해당 컴포넌트만 다시 배치하면 된다.
- 즉, 변경이 시스템에 미치는 영향이 아주 작아지므로 현장에서 시스템을 유지보수하기가 한결 수월하게 된다.
G8: 과도한 정보
- 부실하게 정의된 모듈은 인터페이스가 구질구질하다.
- 간단한 동작 하나에도 온갖 인터페이스가 필요하다.
- 반드시 호출해야 하는 온갖 함수를 제공하므로 결합도가 높다.
- 잘 정의된 인터페이스는 많은 함수를 제공하지 않는다.
- 클래스나 모듈 인터페이스에 노출할 함수를 제한할 줄 알아야 한다.
- 클래스가 제공하는 메서드 수는 작을수록 좋다.
- 함수가 아는 변수 수는 작을수록 좋다.
- 클래스에 들어있는 인스턴스 변수 수는 작을수록 좋다.
- 정보를 제한해 결합도를 낮춰라.
- 자료, 유틸리티 함수, 상수와 임시 변수를 숨겨라.
- 인터페이스를 매우 작게 그리고 매우 깐깐하게 만들어라.
- 메서드나 인스턴스가 넘쳐나는 클래스는 피하라.
- 하위 클래스에서 필요하다는 이유로 protected 변수나 함수를 마구 생성하지 마라.
G9: 죽은 코드
- 죽은 코드란 실행되지 않는 코드를 가리킨다.
- 불가능한 조건을 확인하는 if문
- throw 문이 없는 try 문에서 catch 블록
- 아무도 호출하지 않는 유틸리티 함수와 switch/case 문에서 불가능한 case 조건
- 죽은 코드는 설계가 변해도 제대로 수정되지 않는다.
- 죽은 코드는 시간이 지나면 악취를 풍기기 시작하며, 죽은 지 오래될수록 악취는 강해진다.
- 컴파일은 되지만 새로운 규칙이나 표기법을 따르지 않는다.
- 죽은 코드를 발견하면 시스템에서 제거하라.
G10: 수직 분리
- 변수와 함수는 사용하는 위치에 가깝게 정의한다.
- 지역 변수는 처음으로 사용하기 직전에 선언하며 수직으로 가까운 곳에 위치해야 한다.
- 비공개 함수는 처음으로 호출된 직후에 정의한다.
- 비공개 함수는 전체 클래스 범위(scope)에 속하지만 그래도 정의하는 위치와 호출하는 위치를 가깝게 유지한다.
G11: 일관성 부족
- 어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다.
- 앞서 언급한 ‘최소 놀람의 원칙’에도 부합한다.
- 표기법은 신중하게 선택하며, 일단 선택한 표기법은 신중하게 따른다.
G12: 잡동사니
- 소스 파일은 언제나 깔끔하게 정리해야 한다.
- 잡동사니를 없애라.
- 비어 있는 기본 생성자
- 아무도 사용하지 않는 변수
- 아무도 호출하지 않는 함수
- 정보를 제공하지 못하는 주석
G13: 인위적 결합
- 서로 무관한 개념을 인위적으로 결합하지 않는다.
- 일반적인 enum은 특정 클래스에 속할 이유가 없다.
- 범용 static 함수도 마찬가지로 특정 클래스에 속할 이유가 없다.
- 일반적으로 인위적인 결합은 직접적인 상호작용이 없는 두 모듈 사이에서 일어난다.
- 뚜렷한 목적 없이 변수, 상수, 함수를 당장 편한 위치(=잘못된 위치)에 넣어버린 결과다.
G14: 기능 욕심
- 마틴 파울러가 말하는 코드 냄새 중 하나다.
- 클래스 메서드는 다른 클래스의 변수와 함수에 관심을 가져서는 안 된다.
- 메서드가 다른 객체의 참조자(accessor)와 변경자(mutator)를 사용해 그 객체 내용을 조작한다면 메서드가 그 객체 클래스의 범위를 욕심내는 탓이다.
- 기능 욕심은 한 클래스의 속사정을 다른 클래스에 노출하므로, 별다른 문제가 없다면 제거하는 편이 좋다.
G15: 선택자 인수
- 선택자(selector) 인수는 목적을 기억하기 어려울 뿐 아니라 각 선택자 인수가 여러 함수를 하나로 조합한다.
- 선택자 인수는 큰 함수를 작은 함수 여럿으로 쪼개지 않으려는 게으름의 소신이다.
- bool 인수, enum, int 등 함수 동작을 제어하려는 인수는 하나 같이 바람직하지 않다.
- 일반적으로 인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다.
G16: 모호한 의도
- 코드를 짤 때는 의도를 최대한 분명히 밝힌다.
- 저자의 의도를 흐리는 요소들
- 행을 바꾸지 않고 표현한 수식
- 헝가리식 표기법
- 매직 번호 등
G17: 잘못 지운 책임
- 소프트웨어 개발자가 내리는 가장 중요한 결정 중 하나가 코드를 배치하는 위치다.
- 여기서도 최소 놀람의 원칙을 적용한다.
- 코드는 독자가 자연스럽게 기대할 위치에 배치한다.
G18: 부적절한 static 함수
- 좋은 static 메서드의 예시로는
Math.max(double a, double b)
가 있다.
- 특정 객체(인스턴스)와 관련된 기능이 아니다.
- 메서드가 사용하는 정보는 두 인수가 전부로, 메서드를 소유하는 객체에서 가져오는 정보가 아니다.
Math.max
메서드를 재정의할 가능성은 전혀 없다.
- static으로 정의하면 안 되는 함수를 static으로 정의해서는 안 된다.
- 예를 들어, 특정 객체와 관련이 없으면서 모든 정보를 인수에서 가져오지만 함수를 재정의할 가능성이 존재하는 메소드
- 일반적으로 static 함수보다 인스턴스 하수가 더 좋다.
- 조금이라도 의심스럽다면 인스턴스 함수로 정의한다.
- 반드시 static 함수로 정의해야겠다면 재정의할 가능성은 없는지 꼼꼼히 따져본다.
G19: 서술적 변수
- 켄트 벡이 여러 책에서 지적하는 문제다.
- Smalltalk Best Practice Patterns
- Implementation Patterns
- 프로그램 가독성을 높이는 가장 효과적인 방법 중 하나가 계산을 여러 단계로 나누고 중간 값으로 서술적인 변수 이름을 사용하는 방법이다.
- 서술적인 변수 이름은 많이 써도 괜찮다. 일반적으로는 많을수록 더 좋다.
G20: 이름과 기능이 일치하는 함수
- 이름만으로 분명하지 않기에 구현을 살피거나 문서를 뒤적여야 한다면 더 좋은 이름으로 바꾸거나 아니면 더 좋은 이름을 붙이기 쉽도록 기능을 정리해야 한다.
G21: 알고리즘을 이해하라
- 구현이 끝났다고 선언하기 전에 함수가 돌아가는 방식을 확실히 이해하는지 확인하라.
- 테스트 케이스를 모두 통과한다는 사실만으로 부족하다.
- 작성자가 알고리즘이 올바르다는 사실을 알아야 한다.
- 알고리즘이 올바르다는 사실을 확인하고 이해하려면 기능이 뻔히 보일 정도로 함수를 깔끔하고 명확하게 재구성하는 방법이 최고다.
G22: 논리적 의존성은 물리적으로 드러내라
- 한 모듈이 다른 모듈에 의존한다면 물리적인 의존성도 있어야 한다.
- 의존하는 모듈이 상대 모듈에 대해 뭔가를 가정하면, 즉 논리적으로 의존하면 안 된다.
- 의존하는 모든 정보를 명시적으로 요청하는 편이 좋다.
G23: If/Else 혹은 Switch/Case 문보다 다형성을 사용하라
- switch를 사용하기 전에 다형성을 먼저 고려하라.
- 대다수 개발자가 swtich 문을 사용하는 이유는 그 상황에서 가장 올바른 선택이기 보다는 당장 손쉬운 선택이기 때문이다.
- 유형보다 함수가 더 쉽게 변하는 경우는 극히 드물다.
- 그러므로 모든 swtich 문을 의심해야 한다.
- ‘switch 문 하나’ 규칙
- 선택 유형 하나에는 switch 문을 한 번만 사용한다.
- 같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 switch 문을 대신한다.
G24: 표준 표기법을 따르라
- 팀은 업계 표준에 기반한 구현 표준을 따라야 한다.
- 표준을 명시하는 문서는 코드 자체로 충분해야 하며, 별도 문서를 만들 필요는 없어야 한다.
- 구현 표준이 명시해야 하는 것들
- 인스턴스 변수 이름을 선언하는 위치
- 클래스/메서드/변수 이름을 정하는 방법
- 괄호를 넣은 위치 등
- 팀이 정한 표준은 팀원들 모두가 따라야 한다.
G25: 매직 숫자는 명명된 숫자로 교체하라
- 아마 소프트웨어 개발에서 가장 오래된 규칙 중 하나라 여겨진다.
- 일반적으로 코드에서 숫자를 사용하지 말라는 규칙이다.
- ‘매직 숫자’라는 용어는 숫자 뿐만 아니라 의미가 분명하지 않은 토큰을 모두 가리킨다.
- 테스트 케이스에서 하드 코딩하여 쓴 직원 이름 등
G26: 정확하라
- 검색 결과 중 첫 번째 결과만 유일한 결과로 간주하는 행동은 순진하다.
- 갱신할 가능성이 희박하다고 잠금과 트랜잭션 관리를 건너뛰는 행동은 게으름이다.
- 코드에서 뭔가를 결정할 때는 정확히 결정한다.
- 결정을 내리는 이유와 예외를 처리할 방법을 분명히 알아야 한다.
- 호출하는 함수가 null을 반환할지도 모른다면 null을 반드시 점검한다.
- 조회 결과가 하나뿐이라 짐작한다면 하나인지 확실히 확인한다.
- 통화를 다뤄야 한다면 정수(정수를 사용하는 Money 클래스가 더 좋다)를 사용하고 반올림을 올바로 처리한다.
- 병행(concurrent) 특성으로 인해 동시에 갱신할 가능성이 있다면 잠금 매커니즘을 구현한다.
G27: 관례보다 구조를 사용하라
- 설계 결정을 강제할 때는 규칙보다 관례를 사용한다. 명명 관례도 좋지만 구조 자체로 강제하면 더 좋다.
G28: 조건을 캡슐화하라
- 부울 논리는 이해하기 어렵다.
- 조건의 의도를 분명히 밝히는 함수로 표현하라.
G29: 부정 조건은 피하라
- 부정 조건은 긍정 조건보다 이해하기 어려우므로, 가능하면 긍정 조건으로 표현한다.
G30: 함수는 한 가지만 해야 한다.
- 한 가지만 수행하는 좀 더 작은 함 수 여럿으로 나눠야 한다.
- 3장 “한 가지만 해라” 참조
G31: 숨겨진 시간적인 결합
- 때로는 시간적인 결합이 필요한데, 시간적인 결합을 숨겨서는 안 된다.
- 함수가 실행되는 순서가 중요한 경우
- 순서를 바꿔 호출할 수 없어야 한다.
G32: 일관성을 유지하라
- 코드 구조를 잡을 때는 이유를 고민하고, 그 이유를 코드 구조로 명백히 표현하라.
- 구조에 일관성이 없어 보인다면 남들이 마음대로 바꿔도 괜찮다고 생각한다.
- 예를 들어, 다른 클래스의 유틸리티가 아닌 public 클래스는 자신이 아닌 클래스 범위 안에서 선언하면 안 된다.
- 패키지 최상위 수준에 public 클래스로 선언하는 관례가 일반적이다.
G33: 경계 조건을 캡슐화하라
- 경계 조건은 빼놓거나 놓치기 십상인데, 한 곳에서 별도로 처리하도록 한다.
- 예를 들어, 코드 여기저기에 +1이나 -1을 흩어놓지 않는다.
G34: 함수는 추상화 수준을 한 단계만 내려가야 한다.
- 함수 내 모든 문장은 추상화 수준이 동일해야 한다.
- 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다.
G35: 설정 정보는 최상위 단계에 둬라
- 추상화 최상위 단계에 둬야 할 기본값 상수나 설정 관련 상수를 저차원 함수에 숨겨서는 안 된다.
- 대신 고차원 함수에서 저차원 함수를 호출할 대 인수로 넘긴다.
G36: 추이적 탐색을 피하라
- 일반적으로 한 모듈은 주변 모듈을 모를수록 좋다.
- A가 B를 사용하고 B가 C를 사용한다 하더라도 A가 C를 알아야 할 필요는 없다.
- 이러면 a.getB().getC()를 모두 찾아 a.getB().getQ().getC()로 변경해야 하므로, B와 C 사이에 Q를 넣기가 쉽지 않다.
- 이를 디미터의 법칙(Law of Demeter)이라 부른다.
- 실용주의 프로그래머들은 ‘부끄럼 타는 코드 작성(Writing Shy Code)’이라고도 한다.
- 요지는 자신이 직접 사용하는 모듈만 알아야 한다는 뜻이다.
- 내가 사용하는 모듈이 내게 필요한 서비스를 모두 제공해야 한다.
- 원하는 메서드를 찾느라 객체 그래프를 따라 시스템을 탐색할 필요가 없어야 한다.
자바
J1: 긴 import 목록을 피하고 와일드카드를 사용하라
- 긴 import 목록은 읽기에 부담스럽다.
- 명시적인 import 문은 강한 의존성을 생성하지만 와일드카드는 그렇지 않다.
- 명시적으로 클래스를 import하면 그 클래스가 반드시 있어야 한다.
- 와일드카드를 사용하면 import 문은 패키지를 단순히 검색 경로에 추가하므로 진정한 의존성이 생기지 않는다. → 모듈 간 결합성이 낮아짐
- 명시적으로 import 문을 길게 나열해야 하는 경우도 있다.
- 와일드카드 import 문은 때로 이름 충돌이나 모호성을 초래한다.
- 이름이 같으나 패키지가 다른 클래스는 명시적인 import 문을 사용하거나, 코드에서 클래스를 사용할 대 전체 경로를 명시한다.
- 이 또한 번거롭지만 자주 발생하지 않는다.
J2: 상수는 상속하지 않는다
- 예를 들어, 상수를 인터페이스에 넣은 다음 그 인터페이스를 상속해 해당 상수를 사용한다.
- 상속을 이렇게 사용하면 언어의 범위 규칙을 속이는 행위다.
- 대신 static import를 사용하라.
J3: 상수 대 Enum
- 자바 5에서 제공하는 enum을 마음껏 활용하라!
public static final int
라는 옛날 기교에서, int는 코드에서 의미를 잃어버리기도 한다.
- enum은 이름이 부여된 열거체(enumeration)에 속하기 때문에 그렇지 않다.
- enum은 메서드와 필드도 사용할 수 있으므로 int보다 훨씬 더 유연하고 서술적인 강력한 도구다.
이름
N1: 서술적인 이름을 사용하라
- 소프트웨어 가독성의 90%는 이름이 결정한다.
- 그러므로 시간을 들여 현명한 이름을 선택하고 유효한 상태로 유지한다.
- 소프트웨어가 진화하면 의미도 변하므로 선택한 이름이 적합한지 자주 되돌아본다
- 신중하게 선택한 이름은 추가 설명을 포함한 코드보다 강력하다.
- 신중하게 선택한 이름을 보고 독자는 모듈 내 다른 함수가 하는 일을 짐작한다.
N2: 적절한 추상화 수준에서 이름을 선택하라
- 구현을 드러내는 이름은 피하라
- 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라
N3: 가능하다면 표준 명명법을 사용하라
- 기존 명명법을 사용하는 이름은 이해하기 더 쉽다
- 패턴은 한 가지 표준에 불과하다.
- 예를 들어, 자바에서 객체를 문자열로 반환하는 함수는
toString
이라는 이름을 많이 쓴다.
- 이런 이름은 관례를 따르는 편이 좋다.
- 흔히 팀마다 특정 프로젝트에 적용할 표준을 나름대로 고안한다.
- 에릭 에반스는 이를 프로젝트의 유비쿼터스 언어(ubiquitous language)라 부른다.
- 코드는 이 언어에 속하는 용어를 열심히 써야 한다.
N4: 명확한 이름
- 함수나 변수의 목적을 명확히 밝히는 이름을 선택한다.
- 긴 이름의 함수도, 길다는 단점을 서술성이 충분히 메꾼다.
N5: 긴 범위는 긴 이름을 사용하라
- 이름 길이는 범위 길이에 비례해야 한다.
- 범위가 작으면 아주 짧은 이름을 사용해도 괜찮지만, 범위가 길어지면 긴 이름을 사용한다.
- 범위가 5줄 안팎이라면 i나 j와 같은 변수 이름도 괜찮다.
N6: 인코딩을 피하라
- 이름에 유형 정보나 범위 정보를 넣어서는 안 된다.
m_
이나 f
와 같은 접두어
- 프로젝트 이름이나 하위 시스템 이름에
vis_
와 같은 접두어
- 오늘날 환경은 이름을 조작하지 않고도 모든 정보를 제공한다.
- 헝가리안 표기법의 오염에서 이름을 보호하라.
N7: 이름으로 부수 효과를 설명하라
- 함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용한다.
테스트
T1: 불충분한 테스트
- 테스트 케이스는 잠재적으로 깨질 만한 부분을 모두 테스트해야 한다.
- 테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불완전하다.
T2: 커버리지 도구를 사용하라
- 커버리지 도구는 테스트가 빠트리는 공백을 알려준다.
- 테스트가 불충분한 모듈, 클래스, 함수를 찾기가 쉬워진다.
- 대다수 IDE는 테스트 커버리지를 시각적으로 표현해주므로, 전혀 실행되지 않는 if 혹은 case 문 블록이 금방 드러난다.
T3: 사소한 테스트를 건너뛰지 마라
- 사소한 테스트는 짜기 쉽다.
- 사소한 테스트가 제공하는 문서적 가치는 구현에 드는 비용을 넘어선다.
T4: 무시한 테스트는 모호함을 뜻한다
- 불분명한 요구사항은 테스트 케이스를 주석으로 처리하거나 테스트 케이스에
@Ignore
를 붙여 표현한다.
- 선택 기준은 모호함이 존재하는 테스트 케이스가 컴파일이 가능한지 불가능한지에 달려있다.
T5: 경계 조건을 테스트하라
- 경계 조건에서 실수하는 경우가 흔하므로, 각별히 신경 써서 테스트한다.
T6: 버그 주변은 철저히 테스트하라
- 버그는 서로 모이는 경향이 있다.
- 한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트하는 편이 좋다.
T7: 실패 패턴을 살펴라
- 때로는 테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다.
- 테스트 케이스를 최대한 꼼꼼히 짜라는 이유도 여기에 있다.
T8: 테스트 커버리지 패턴을 살펴라
- 통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러난다.
T9: 테스트는 빨라야 한다
- 느린 테스트 케이스는 실행하지 않게 된다.
- 그러므로 테스트 케이스가 빨리 돌아가게 최대한 노력한다.
Reference
참고 서적
📔 Clean Code