Heruistics(발견법): 경험, 학습 등으로 습득한 직감을 이용해 현실적으로 만족할 만한 수준의 해답을 찾는 것
리펙토링(마틴 파울러)에서 언급된 내용과 저자의 경험을 합쳐 깨끗하지 못한 코드를 발견하는 방법에 대하여 설명했다.
아래에 해당된다면 깨끗한 코드인지 고민해서 확인해볼 필요가 있다.
주석
C1: 부적절한 정보
주석은 코드와 설계에 기술적인 설명을 부연하는 수단이다.
다른 시스템에 저장할 정보는 주석으로 적절하지 못하다.
- 소스 코드 관리 시스템
- 버그 추적 시스템
- 이슈 추적 시스템
- 기타 기록 관리 프로그램 등
예를 들어, 변경 이력은 과도한 정보로 코드를 번잡하게 만든다.
C2: 쓸모없는 주석
쓸모 없는 주석은 일단 들어가고 나면 업데이트를 주저하기 때문에 변해가는 코드에서 쉽게 멀어지고 코드를 그릇된 방향으로 이끌 여지가 있다.
오래된 주석, 엉뚱한 주석, 잘못된 주석은 더 이상 쓸모가 없다.
주석은 빨리 낡는다. 따라서 쓸모 없어질 주석은 달지 않는 편이 좋다. 또한 쓸모 없어진 주석은 재빨리 삭제하는 편이 가장 좋다.
C3: 중복된 주석
주석은 코드만으로 다하지 못하는 설명을 부연하는 역할을 해야한다.
서술적인 이름 등 깨끗한 코드로 설명할 수 있는 내용으로 주석을 만들어 중복되는 설명을 만드는 주석은 사용하지 않는다.
C4: 성의없는 주석
꼭 필요한 주석은 잘 작성해야할 의무가 있다.
- 단어를 신중하게 선택한다.
- 문법과 구두점을 올바로 사용한다.
- 주절대지 않는다.
- 당연한 소리를 반복하지 않는다.
- 간결하고 명료하게 작성한다.
C5: 주석 처리된 코드
주석 처리된 코드는 만들어지면 코드의 중요도나 의미와 관계없이, (얼마나 오래된 코드인지, 중요한 코드인지) 누군가가 사용할수도 있을 것이라는 생각 때문에삭제하기 꺼려진다.
결국 이렇게 남겨진 코드는 읽는 사람을 혼동하게 하는 등 코드를 오염시킨다.
주석으로 처리된 코드를 발견하면 즉각 지워라! 누군가 필요로 한다면 버전관리 시스템으로 확인할 수 있다.
주석으로 처리된 코드는 대부분 당장 필요한 경우가 없어서 이전에 필요한 사람도 오랜 기간 후에 소수의 사람일 가능성이 높다. 따라서 주석으로 남겨 코드를 오염시켜 사람들을 혼동시키는 것 보다. 버전관리 시스템을 통해 필요한 사람들이 찾게 남기는 것이 더 올바른 선택이다.
환경
E1: 여러 단계로 빌드해야 한다.
빌드는 간단히 한 단계로 끝나야 한다. 기타 시스템에 필요한 파일을 찾느라 여기저기 뒤적을 필요가 없어야 한다. 한 명령으로 전체를 체크아웃해서 한 명령으로 빌드할 수 있어야 한다.
E2: 여러 단계로 테스트해야 한다.
테스트 자체가 귀찮은 행위이다. 따라서 간단하고 빠르고 결과가 명료해야 자주 자주 활용될 수 있고, 이를 통해 코드의 품질, 테스트를 유지할 수 있다.
모든 단위테스트는 한 명령으로 돌려야 한다. IDE에서 버튼 하나로 모든 테스트를 돌린다면 가장 이상적이다. 아무리 열악한 환경이라도 셸에서 명령 하나로 가능해야 한다.
함수
F1: 너무 많은 인수
함수에서 인수 개수는 적을수록 고민할 사항들이 적어진다. 따라서 가능하다면 인수가 없는게 좋고 적에 만드는 것이 좋다.
인수가 4개 이상일 경우 인수가 정말로 필요한지 고민해봐야 한다.
F2: 출력 인수
출력 인수: 함수 입력으로 사용된 인수가 출력이 되는 경우
일반적으로 독자는 출력이 아닌 입력으로 생각한다. 함수에서 어떠한 상태를 변경해야 한다면, 변경 대상을 인수로 넣어 사용하는 것이 아니라, 객체의 상태를 표현하게 만들고 함수 내부에서 this
같은 키워드를 통해 객체의 상태를 변경해야 한다.
F3: 플래그 인수
Boolean 인수는 함수가 여러 기능(참일때 이거 아닐때 이거)을 수행한다는 명백한 증거다. 플래그 인수는 혼란을 초래하므로 피해야한다.
F4: 죽은 함수
아무도 호출하지 않는 함수는 삭제한다.
일반
G1: 한 소스 파일에 여러 언어를 사용한다.
오늘날 프로그래밍 환경은 한 소스 파일 내에서 다양한 언어를 지원한다.
이상적으로 소스 파일 하나에 언어 하나만 사용하는 방식이 가장 좋으나, 현실적으로 여러 언어가 불가피하다.
하지만 소스 파일에서 언어 수와 범위를 최대한 줄이도록 애써야 한다.
G2: 당연한 동작을 구현하지 않는다.
최소 놀람 원칙(The Principle of Least Surprise): “필요한 기능에 크나큰 깜짝 놀래킬만한 요소가 있다면 해당 기능을 다시 설계할 필요가 있을 수 있다”
함수나 클래스는 다른 프로그래머가 당연하게 여길만한 동작과 기능을 제공해야 한다.
G3: 경계를 올바르게 처리하지 않는다.
코드를 올바르게 처리하는것은 당연하지만 복잡하다는 것을 관과하고 직관에 의존하여 만든다. 이러한 과정에서 모든 경계와 구석진 곳에서 증명하지 않는다.
부지런함을 대신할 지름길은 없다. 모든 경계조건, 모든 구석진 곳, 모든 예외는 우아하고 직관적인 알고리즘을 좌초시킬 암초다.
스스로의 직관에 의존하지 않고, 모든 경계조건을 찾아내 테스트 하는 테스트 케이스를 만들어야 한다.
G4: 안전 절차 무시.
실패하는 테스트 케이스를 제껴두고 나중으로 미루는 태도는 신용카드가 공짜 돈이라는 생각만큼 위험하다.
G5: 중복
DRY(Don’t Reapeat Yourself): 익스트림 프로그래밍의 핵심 규칙중 하나로 선언한 후 “한 번, 단 한번만(Once, and only once)” 론 제프리스는 이 규칙을 “모든 테스트를 통과한다” 규칙 다음으로 중요하게 꼽았다.
코드에서 중복을 발견할 때마다 추상화할 기회로 간주하라. 중복된 코드르 하위 루틴이나 다른 클래스로 분리하는 등 추상화로 중복을 정리하면 설계 언어의 어휘가 늘어난다.
G6: 추상화 수준이 올바르지 못하다.
기초 클래스는 구현 정보를 몰라야 한다. (구현에 관계없이 의도한 결과만 받아오고, 이름을 통해 결과물을 잘 표현해야 한다.)
추상화는 저차원 상세 개념에서 고차원 일반 개념을 분리한다.
추상화로 개념을 분리할 때는 모든 저차원 개념을 파생 클래스에 넣고, 모든 고차원 개념은 기초 클래스에 넣는다.
- 세부 구현과 관련한 상수, 변수, 유틸리티 함수는 기초 클래스에 넣으면 안된다.
|
|
- percentFulll 함수는 추상화 수준이 올바르지 못함
- 꽉 찬 정도를 의미하는 결과물을 반환하는데, 경우에 따라서 (ex. 크기가 무한대) 꽉 찬 정도를 알아낼 수 없다. 그러므로 BoundedStack 같은 파생 인터페이스에 넣어야 마땅하다.
- stack의 크기가 논리적으로 무한한 경우 0을 반환하면 된다고 하지만 물리적으로 무한할 수 없다.
|
|
- 위 코드의 경우 스택 크기를 확인할때 OutOfMemoryException 예외가 발생할 여지가 있다.
- 또한 0을 반환하면 거짓된 정보를 반환하게 된다.
- 일반적으로 stack을 구현할 때 크기를 저장할 변수를 만듦.
- push 할때 크기값을 증가시키므로 한계를 넘어서는 갯수를 저장할 때 예외가 발생시킬수 있음
- percentFull을 구현한다고 하더라도, 무한한 경우를 검사해서 예외를 발생시킬 수 있음
G7: 기초 클래스가 파생 클래스에 의존한다.
개념을 기초 클래스와 파생 클래스로 나누는 가장 흔한 이유는 고차원 기초 클래스 개념을 저차원 파생 클래스 개념으로부터 분리해 독립성을 보장하기 위해서다.
따라서 기초 클래스는 파생 클래스를 사용한다면 뭔가 문제가 있다는 말이다.
일반적으로는 기초 클래스와 파생 클래스를 다른 파일로 배포하는 편이 좋다.
- 독립적인 개별 컴포넌트 단위로 시스템을 배치할 수 있다.
- 컴포넌트를 변경한다면 해당 컴포넌트만 다시 배치하면 된다.
즉, 변경이 시스템에 미치는 영향이 아주 작아지므로 현장에서 시스템을 유지보수하기가 한결 수월하게 된다.
G8: 과도한 정보
클래스나 모듈 인터페이스에 노출할 함수를 제한하여 필요한 기능만 활용하도록 한다.
자료와 함수를 최대한 숨기고 필요한 기능만 깐깐하게 공개하여 결합도를 높힐 여지를 만들지 마라
잘 정의된 모듈은 인터페이스가 아주 작다. 작은 인터페이스로도 많은 동작을 구현할 수 있다.
반면 부식하게 정의된 모듈은 인터페이스가 구질구질하다. 그래서 간단한 동작 하나에도 온갖 인터페이스가 필요하다.
잘 정의된 인터페이스는 많은 함수를 제공하지 않는다. 함수를 적게 제공하기 때문에 (결합도를 높힐 여지가 적으므로) 결합도가 자연스럽게 낮아진다.
- 클래스가 제공하는 메서드 수는 작을수록 좋다.
- 함수가 아는 변수 수도 작을수록 좋다.
- 클래스에 들어있는 인스턴스 변수 수도 작을수록 좋다.
G9: 죽은 코드
죽은 코드란 실행되지 않는 코드를 가르킨다. 죽은 코드를 발견하면 장례식을 치뤄줘라!
- 불가능한 조건을 확인하는 if 문
- thorw 문이 없는 try 문에서 catch 블록
- 아무도 호출하지 않는 유틸리티 함수
- switch-case 문에서 불가능한 case 조건 등
죽은 코드는 설계가 변해도 제대로 수정되지 않기 때문에 새로운 규칙이나 표기법을 따르지 않아 일관성을 해치거나 레거시로 남게되고, 다른 코드들도 같이 오염시킬 여지를 준다.
G10: 수직 분리
변수와 함수는 사용되는 위치에 가깝게 정의한다.
- 지역 변수는 처음으로 사용하기 직전에 선언하며 수직으로 가까운 곳에 위치해야 한다.
- 비공개 함수는 처음으로 호출함 직후에 정의한다.
G11: 일관성 부족
간단한 일관성만으로도 코드를 읽고 수정하기 쉬워진다.
어떤 개념을 특정 방식으로 구현했다면 유사한 개념도 같은 방식으로 구현한다.
- 최소 놀람 원칙에도 부합한다.
표기법은 신중하게 선택하고, 선택된 표기법을 준수해야 한다.
G12: 잡동사니
- 비어 있는 기본 생성자
- 아무도 사용하지 않는 변수
- 아무도 호출하지 않는 함수
- 정보를 제공하지 못하는 주석
코드만 복잡하게 만들 뿐이므로 제거한다.
G13: 인위적인 결합
함수, 상수, 변수를 선언할 때는 시간을 들여 올바른 위치를 고민한다.
서로 무관한 개념을 인위적으로 결합하지 않는다.
- Enum
- 일반적인 enum은 특정 클래스에 속할 이유가 없다. enum이 클래스에 속하면 사용하는 코드가 특정 클래스를 알아야 한다.
- 범용 static 변수
일반적으로 인위적인 결합은 직접적인 상호작용이 없는 두 모듈 사이에서 일어난다. 뚜렷한 목적이 없이 변수, 상수, 함수를 당장 편한 위치에(잘못된) 넣어버려 발생한다.
G14: 기능 욕심
기능 욕심은 한 클래스의 내부를 다른 클래스에 노출하게 되므로, 별다른 문제가 없다면, 제거하는 것이 좋다.
클래스 메서드는 자기 클래스의 변수와 함수에 관심을 가져야지 다른 클래스의 변수와 함수에 관심을 가져서는 안된다.
메서드가 다른 객체의 참조자와 변경자를 사용해 그 객체 내용을 조작한다면 메서드가 그 객체 클래스의 범위를 욕심내는 탓이다.
|
|
calculateWeeklyPay 메서드가 HourlyEmployee 객체에서 많은 정보를 가져와 처리한다. calculateWeeklyPay 메서드는 HourlyEmployee 클래스의 범위를 욕심낸다고 볼 수 있다. 자신이 HourlyEmployee 클래스에 속하는 것 처럼 구현되어 있다.
HourlyEmployee 클래스에서 적어도 tenthRate, straightTime, overTime을 반환하는 것이 적합해보임
G15: 선택자 인수
선택자 인수는 목적을 기억하기 어려울 뿐 아니라 각 선택자 인수가 여러 함수를 하나로 조합한다. 선택자 인수는 큰 함수를 작은 함수 여럿으로 쪼개지 않으려는 게으름의 소산이다.
flag 변수들을 의미한다.
부울 인수, enum, int 등 함수 동작을 제어하려는(분기하려는) 인수는 하나같이 바람직 하지 않다. 일반적으로 인수를 넘겨 동작을 선택하는 대신 새로운 함수를 만드는 편이 좋다.
💡 유사한 개념을 통합해 코드를 줄이기 위해 사용되는 경우를 종종 봐왔는데, 이런 경우 유사한 개념을 분리하고 각각 별도 구현하여 분리한 기능을 호출하여 사용되는 것이 올바른 선택인 것 같다.
G16: 모호한 의도
코드를 짤 때는 의도를 최대한 분명히 밝혀야 한다.
- 행을 바꾸지 않고 표현한 수식
- 헝가리식 표기법(변수 및 함수 인자 이름 앞에 데이터 타입을 명시하는 코딩 규칙)
- 매직 넘버 등
독자에게 의도를 분명히 표현하도록 시간을 투자해야한다.
G17: 잘못 지운(전가한) 책임 (MisPlaced responsibility)
최소 놀람 원칙을 적용하여 기능을 적절한 위치에 배치해야 한다.
독자에게 직관적인 위치가 아니라 개발자에게 편한 함수에 배치하는 것을 비꼬고있다.
다른 위치에 배치될 경우 함수 이름을 잘 지어 확실하게 표현해야한다.
G18: 부적절한 static 함수
반드시 static 함수로 정의해야겠다면 오버라이딩할 가능성이 없는지 꼼꼼히 살핀다.
static 함수
는 오버라이딩 할 가능성이 없어야 한다.
|
|
위 메서드는 특정 객체와 관련이 없으면서 모든 정보를 인수에서 가져오기 때문에 static 함수
로 정의해도 괜찮아 보이지만, 수당을 계산하는 기능이 여러개로 분리될 가능성이 있어(오버라이딩 될 수 있어) 적합하지 않다. 따라서 Employee 클래스에 속하는 인스턴스 함수여야 한다.
일반적으로 static 함수보다 인스턴스 함수가 좋다. 조금이라도 의심스럽다면 인스턴스 함수로 정의한다.
G19: 서술적 변수
계산을 몇 단계로 나누고 중간값에 좋은 변수 이름만 붙여도 읽기 쉬운 코드로 바뀐다.
프로그램 가독성을 높이는 가장 효과적인 방법 중 하나가 계산을 여러 단계로 나누고 중간 값으로 서술적인 변수 이름을 사용하는 방법이다.
[Book] 켄트벡의 구현 패턴 - gpeegpee/learn-java Wiki
|
|
서술적인 변수 이름을 사용하여 match를 이용하여 찾은 첫번째 그룹이 키(key)이고, 두번째로 일치하는 그룹이 값(value)이라는 사실이 명확히 들어난다.
서술적인 변수 이름은 많이 써도 괜찮다.
G20: 이름과 기능이 일치하는 함수
|
|
위 함수는 5일을 더하는 함수인가? 5주 혹은 5시간? date 인스턴스를 변경하는 함수인가? 아니면 예전 date 인스턴스는 두고 새로운 Date를 반환하는 함수인지 알 수 없다.
- date 인스턴스에 5일을 더해 date 인스턴스를 변경하는 함수라면
- addDaysTo 혹은 increaseByDays
- date 인스턴스를 변경하지 않으면서 5일 뒤인 새 날짜를 반환
- daysLater, daysSince
이름만으로 분명하지 않기에 구현을 살피거나 문서를 뒤적여야 한다면 더 좋은 이름으로 바꾸거나 아니면 더 좋은 이름을 붙이기 쉽도록 기능을 정리해야 한다.
G21: 알고리즘을 이해하라
코드가 돌아간다는 사실을 아는 것과 돌아가기 위한 알고리즘이 올바르다는 사실을 아는 것은 다르다.
대다수 괴상한 코드는 사람들이 알고리즘을 충분히 이해하지 않은 채 코드를 구현한 탓이다.
구현이 끝났다고 선언하기 전에 함수가 돌아가는 방식을 확실히 이해하는지 확인하라. 테스트 케이스를 모두 통과한다는 사실만으로 부족하다.
알고리즘이 올바르다는 사실을 확인하고 이해하려면 기능이 뻔히 보일 정도로 함수를 깔끔하고 명확하게 재구성하는 방법이 최고다.
G22: 논리적 의존성은 물리적으로 드러내라
한 모듈이 다른 모듈에 의존한다면 의존하는 모든 정보를 명시적으로 요청하여 물리적인 의존성이 드러낸다.
의존성의 드러나있지 않다면 코드를 한눈에 파악할 수 없어 잘못 구현하거나, 아니더라도 코드를 분석을 해야 하기때문에 유지보수에 어려움이 생긴다.
G23: if-else 혹은 switch/case 문보다 다형성을 사용하라
- 대다수 개발자가 switch문을 사용하는 이유는 그 상황에서 가장 올바른 선택이기보다는 당장 손쉬운 선택이기 때문이다.
- 유형보다 함수가 더 쉽게 변하는 경우는 긱히 드물다.
저자는 ‘switch 문 하나’ 규칙을 따른다. 선택 유형 하나에는 switch문을 한번만 사용한다. 같은 선택을 수행하는 다른 코드에서는 다형성 객체를 생성해 switch 문을 대신한다.
G24: 표준 표기법을 따르라
팀은 업계 표준에 기반한 구현 표준을 따라야 한다.
구현 표준
표준을 설명하는 문서는 코드 자체로 충분해야 하며 업계 표준을 따라르기 때문에 별도 문서를 만들 필요는 없어야 한다.
- 인스턴스 변수 이름을 선언하는 위치
- 클래스, 메서드, 변수 이름을 정하는 방법
- 괄호 넣는 위치
G25: 매직 숫자는 명명된 상수로 교체하라
일반적으로 코드에서 숫자를 사용하지 않는것이 좋다. 사용되는 숫자는 명명된 상수 뒤로 숨겨라
- 매직 숫자라는 용어는 단순히 숫자만 의미하지 않는다. 의미가 분명하지 않은 토큰을 모두 가르킨다.
G26: 정확하라
코드에서 뭔가를 결정할 때는 정확히 결정한다(들어맞게). 결정을 내리는 이유와 예외를 처리할 방법을 분명히 알아야 한다.
호출하는 함수가 null을 반환할 가능성이 있다면 null을 항상 점검한다. 조회 결과가 하나뿐이라 짐작한다면 하나인지 확실히 확인한다.
갱신 가능성이 희박하다고 잠금과 트랜잭션 관리를 건너뛰는 행동은 아무리 잘 봐줘도 게으름이다.
List로 선언할 변수를 ArrayList로 선언하는 행동은 지나친 제약이다.
코드에서 모호성과 부정확은 의견차나 게으름의 결과다.
G27: 관례보다 구조를 사용하라
설계 결정을 강제할 때는 규칙보다 관례를 사용한다. 더 나아가 구조 자체로 강제하면 더 좋다.
- enum을 활용한
switch-case
보다 추상 메서드가 있는 기초 클래스가 더 좋다.
switch-case
문을 매번 똑같이 구현하게 강제하기는 어렵지만, 파생 클래스는 추상 메서드를 모두 구현하지 않으면 안되기 때문이다.
G28: 조건을 캡슐화하라
부울 논리는 이해하기 어렵다. 조건의 의도를 분명히 밝히는 함수로 표현하라.
G29: 부정 조건을 피하라
부정 조건은 긍정 조건보다 이해하기 어렵다. 가능하면 긍정 조건으로 표현한다.
G30: 함수는 한가지만 해야한다
함수를 짜다보면 함수 안에 여러 단락을 이어, 일련의 작업을 수행하고픈 유혹에 빠진다. 이런 함수는 한가지만 수행하는 함수가 아닐 가능성이 높다. 한 가지만 수행하는 좀 더 작은 함수 여럿으로 나눠야 한다.
G31: 숨겨진 시간적인 결합
때로는 시간적인 결합이 필요하다. 하지만 시간적인 결합을 숨겨서는 안된다. 함수를 만들때 함수 인수를 적절히 배치하여 함수가 호출되는 순서를 명백히 드러낸다.
함수 인수를 통해 각 함수가 내놓는 결과를 사용하게 구현하면 일종의 연결 소자 역할을 하게 되어 시간적인 결합을 노출하기 쉽다.
G32: 일관성을 유지하라
코드 구조를 잡을 때는 이유를 고민하라. 그리고 그 이유를 코드 구조로 명백히 표현하라.
구조에 일관성이 없어 보인다면 남들이 맘대로 바꿔도 괜찮다고 생각한다. 반면 시스템 전반에 걸쳐 구조가 일관성이 있다면 남들도 일관성을 따르고 보존한다.
G33: 경계 조건을 캡슐화하라
경계 조건은 빼먹거나 놓치기 쉽다. 따라서 경계 조건을 코드 여기저기세서 처리하지 않고 한 곳에서 별도로 처리한다.
G34: 함수는 추상화 수준을 한 단계만 내려가야 한다
함수 내 모든 문장은 추상화 수준이 동일해야 한다. 그리고 추상화 수준은 함수 이름이 의미하는 작업보다 한 단계만 낮아야 한다.
- 개념은 아주 간단하지만 인간은 추강화 수준을 뒤섞는 능력이 너무나도 뛰어나 따르기 어렵다.
|
|
위 함수는 추상화 수준이 두개 이상 섞여있다.
- 수평선에 크기가 있다는 개념
- hr 태그 자체의 문법
|
|
G35: 설정 정보는 최상위 단계에 둬라
추상화 최상위 단계에 뒤야 할 기본값 상수나 설정 관련 상수를 저차운 함수에 숨겨서는 안된다. 대신 고차원 함수에서 저차원 함수를 호출할 때 인수로 넘긴다.
G36: 추이적 탐색을 피하라
일반적으로 한 모듈은 주변 모듈을 모를수록 좋다. 좀 더 구체적으로 A가 B를 사용하고, B가 C를 사용한다 하더라도 A가 C를 알아야 할 필요는 없다는 뜻이다.
- ex. a.getB().getC().doSometing();
이를 “디미터의 법칙”, “부끄럼 타는 코드 작성”이라 부른다.
요지는 자신이 직접 사용하는 모듈만 알아야 한다는 뜻이다. 내가 아는 모듈이 연이어 자신이 아는 모듈을 따라가며 시스템 전체를 휘저을 필요가 없다.
Java
J1: 긴 import 목록을 피하고 와일드카드를 사용하라.
J2: 상수는 상속하지 않는다.
상수를 상속하여 사용하는 것은 언어의 범위 규칙을 속이는 행위이다. 대신 static import를 사용하라
J3: 상수 VS enum
enum을 사용하면 public static final int
라는 옛날 기교를 더 이상 사용할 필요가 없다. int는 코드에서 의미를 잃어버리기도 한다.
반면 enum은 이름이 부여된 열거체이므로 의미를 잃어버리지 않는다.
메서드와 필드도 사용할 수 있다. int보다 훨씬 더 유연하고 서술적이다.
이름
N1: 서술적인 이름을 사용하라
소프트웨어의 가독성은 90% 이름이 결정한다. 그러므로 시간을 들여 현명한 이름을 선택하고 유효한 상태로 유지한다.
- 서술적인 이름을 신중히 고른다.
- 소프트웨어가 진화하면 의미도 변하므로 선택한 이름이 적합한지 자주 되돌아본다.
N2: 적절한 추상화 수준에서 이름을 선택하라
구현을 드러내는 이름은 피하는 것이 좋다. 작업 대상 클래스나 함수가 위치하는 추상화 수준을 반영하는 이름을 선택하라.
N3: 가능하다면 표준 명명법을 사용하라
기존 명명법을 사용하는 이름은 이해하기 더 쉽다.
- 디자인 패턴을 활용하면 클래스 이름에 패턴 이름을 사용한다.
- 자바에서 객체를 문자열로 변환하는 함수는 toString이라는 이름을 쓴다. 등
이름에 기존 관례가 있다면 관례를 따르는 것이 좋다.
프로젝트에 유효한 의미가 담긴 이름을 많이 사용할수록 독자가 코드를 이해하기 쉬워진다.
N4: 명확한 이름
함수나 변수의 목적을 명확히 밝히는 이름을 선택한다. 광범위하거나 모호한 이름을 사용하지 않는다.
N5: 긴 범위는 긴 이름을 사용하라
이름 길이는 범위 길이에 비례해야 한다. 범위가 작으면 아주 짧은 이름을 사용해도 괜찮다. 하지만 범위가 길어지면 긴 이름을 사용한다.
N6: 인코딩을 피하라
이름에 유형 정보나 범위 정보를 넣어서는 안 된다.
N7: 이름으로 부수 효과를 설명하라
함수, 변수, 클래스가 하는 일을 모두 기술하는 이름을 사용한다. 이름에 부수 효과를 숨기지 않는다.
테스트
T1: 불충분한 테스트
테스트 케이스는 잠재적으로 깨질 만한 부분을 보두 테스트해야 한다. 테스트 케이스가 확인하지 않는 조건이나 검증하지 않는 계산이 있다면 그 테스트는 불완전하다.
T2: 커버리지 도구를 사용하라
커버리지 도구는 테스트가 빠뜨리는 공백을 알려준다. 도구를 사용하며 ㄴ테스트가 불충분한 모듈, 클래스, 함수를 찾기가 쉬워진다.
상위 15 개 코드 커버리지 도구 (Java, JavaScript, C ++, C #, PHP 용) - 다른
T3: 사소한 테스트를 건너뛰지 마라
사소한 테스트는 짜기 쉽다. 사소한 테스트가 제공하는 문서적 가치는 구현에 드는 비용을 넘어선다.
T4: 무시한 테스트는 모호함을 뜻한다
때로는 요구사항이 불분명하기에 프로그램이 돌아가는 방식을 확신하기 어렵다. 불분명한 요구사항은 테스트 케이스를 주석으로 처리하거나 테스트 케이스에 @Ignore를 붙여 표현한다.
선택 기준은 모호함이 존재하는 테스트 케이스가 컴파일이 가능한지 불가능한지에 달려있다.
T5: 경계 조건을 테스트하라
경계 조건은 각별히 신경 써서 테스트한다. 알고리즘의 중앙 조건은 올바로 짜놓고 경계 조건에서 실수하는 경우가 흔하기 때문
T6: 버그 주변은 철저히 테스트하라
버그는 서로 모이는 경향이 있다. 한 함수에서 버그를 발견했다면 그 함수를 철저히 테스트하는 편이 좋다.
T7: 실패 패턴을 살펴라
때로는 테스트 케이스가 실패하는 패턴으로 문제를 진단할 수 있다.
합리적인 순서로 정렬된 꼼꼼한 테스트 케이스는 실패 패턴을 드러낸다.
T8: 테스트 커버리지 패턴을 살펴라
통과하는 테스트가 실행하거나 실행하지 않는 코드를 살펴보면 실패하는 테스트 케이스의 실패 원인이 드러난다.
T9: 테스트는 빨라야한다
느린 테스트 케이스는 실행하지 않게 된다. 일정이 촉박하면 느린 테스트 케이스를 제일 먼저 건너 뛴다.
결론
휴리스틱 목록을 익힌다고 소프트웨어 장인이 되지는 못한다. 전문가 정신과 장인 정신은 가치에서 나온다. 그 가치에 기반한 규율과 절제가 필요하다.