1장 깨끗한 코드
C++ 창시자이자 C++ Programming Language 저자 Bjarne Stroustrup
- 나는 우아하고 효율적인 코드를 좋아한다.
- 논리가 간단해야 버그가 숨어들지 못한다.
- 의존성을 최대한 줄여야 유지보수가 쉬워진다.
- 오류는 명백한 전략에 의거해 철저히 처리한다.
- 성능을 최적으로 유지해야 사람들이 원칙 없는 최적화로 코드를 망치려는 유혹에 빠지지 않는다.
- 깨끗한 코드는 한 가지를 제대로 한다.
Object Oriented Analysis and Design with Application 저자 Grady Booch
- 깨끗한 코드는 단순하고 직접적이다.
- 깨끗한 코드는 결코 설계자의 의도를 숨기지 않는다.
- 오히려 명백한 추상화와 단순한 제어문으로 가득하다.
OTI 창립자이자 이클립스 전략의 대부 Dave Thomas
- 깨끗한 코드는 작성자가 아닌 사람도 읽기 쉽고 고치기 쉽다.
- 단위 테스트 케이스와 인수 테스트 케이스가 존재한다.
- 깨끗한 코드에는 의미있는 이름이 붙는다.
- 특정 목정을 달성하는 방법은 하나만 제공한다.
- API 는 명확하며 최소로 줄였다.
- 언어에 따라 필요한 모든 정보를 코드만으로 명확히 표현할 수 없기에 코드는 문학적으로 표현해야 마땅하다.
Working Effectively with Lagacy Code 저자 Michael Feathers
- 깨끗한 코드의 특징은 많지만 그 중에서도 모두를 아우르는 특징이 하나 있다.
- 깨끗한 코드는 언제나 누군가 주의 깊게 짰다는 느낌을 준다.
- 작성자가 이미 모든 사항을 고려했으므로, 고칠 궁리를 하다보면 언제나 제자리로 돌아온다.
- 그리고는 누군가 남겨준 코드, 누군가 주의 깊게 짜놓은 작품에 감사를 느낀다.
Extreme Programming Installed 와 Extreme Programming Adventure in C# 저자 Ron Jeffries
- 최근 들어 나는 컨트 벡이 제안한 코드 규칙으로 구현을 시작한다.
- 중요한 순으로 나열하자면 간단한 코드는 '모든 테스트를 통과한다.'
- '중복이 없다', '시스템 내 모든 설계 아이디어를 표현한다.'
- '클래스, 메서드, 함수 등을 최대한 줄인다'
위키 장시자, 익스트림 프로그래밍 공동창시자 Ward Cunningham
- 코드를 읽으면서 짐작했던 기능을 각 루팅이 그대로 수행한다면 깨끗한 코드라 불러도 되겠다.
- 코드가 그 문제를 풀기 위한 언어처럼 보인다면 아름다운 코드라 불러도 되겠다.
2장 의미있는 이름
의도를 분명히 밝혀라
- 의도가 분명한 이름은 정말로 중요하다.
- 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다.
- 의도를 전달하는 이름이 무엇일까?
int d; // 경과 시간
- d 라는 변수는 의도를 주석으로 나태내고 있다. 그렇다면 d 가 쓰이는 모든 부분에 주석으로 의도를 명시할 것인가? -> 옳지 않다.
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModeification;
int fileAgeInDays;
- 위와 같이 의도를 표한할 수 있는 변수명을 사용하는 것이 바람직하다.
- 아래와 같은 코드의 문제점은 무엇일까?
public List<int[]> getThem(){
List<int[]> list1 = new ArrayList<int[]>();
for (int[] x : theList)
if (x[0] == 4)
list1.add(x);
return list1;
}
- 위의 코드를 보면 아래와 같은 의문이 생긴다.
- theList에 무엇을 포함하고 있지?
- 0번째값을 왜 찾아야하지?
- 값이 4는 무슨 의미지?
- 함수가 반환하는 리스트 list1는 어떻게 사용하지?
- 위의 코드를 아래와 같이 리팩토링 할 수 있다.
public List<Cell> getFlaggedCells(){
List<Cell> flaggedCells = new ArrayList<Cell>();
for (Cell cell : gameBoard)
if (cell[STATUS_VALUE] == FLAGGED)
flaggedCells.add(cell);
return flaggedCells;
}
- 위와 같이 의미있는 변수명을 부여하고, 매직 넘버를 제거했을 뿐인데, 코드의 의도가 명확해졌다.
의미 있게 구분하라
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i< a1.length; i++) {
a2[i] = a1[i];
}
}
- 위의 코드에서 a1, a2 는 숫자 1,2 로 구분을 했으나, 역시 의도를 파악할 수 없다.
public static void copyChars(char source[], char destination[]) {
for (int i = 0; i< source.length; i++) {
destination[i] = source[i];
}
}
- 위와 같이 source, destination 으로 의도를 전달하는 것이 바람직 하다.
클래스 이름
- 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
- Customer, WikiPage, Account, AddressParser 등은 좋은 예다.
- Manager, Processor, Date, Info와 같은 단어는 피하는 것이 좋다. -> 너무 범용적이다.
- 동사는 사용하지 않는다.
메서드 이름
- 메서드 이름은 동사나 동사구가 적합하다.
- postPayment, deletePage, save 등이 좋은 예다.
- 접근자, 변경자, 조건자는 javabean 표준에 따라 값 앞에 get, set, is를 붙인다.
- 생성자를 중복정의할 때는 정적 팩토리 메서드를 사용한다.
- Complex fulcrumPoint = Complex.FromRealNumber(23.0);
검색하기 쉬운 이름을 사용해라
- 여기서 검색은 구글링이 아닌, IDE 툴에서의 검색을 의미한다.
private static final MAX_CLASSES_PER_STUDENT = "7";
- 문자 "7" 만 사용하는 것 보단 상수를 사용해서 눈에 잘 띄게 구분하는 것이 좋다.
한 개념에 한 단어만 사용하라
- 추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다.
- 똑같은 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다.
- 그렇기 때문에 일관성 단어를 선택하는 것이 바람직하다.
의미있는 맥락을 추가해라
- 이름에 의미를 부여하기 위해서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다.
- 하지만, 때때로 위의 방법이 실패할 때가 있다.
- 그럴때에 마지막 수단으로 접두어를 붙인다.
- firstName, lastName, street, houseNumber, city, state, zipcode 라는 변수가 있다고 해보자.
- 저 변수들이 모여있으면 주소와 관련된 변수라고 인식할 수 있지만, firstName, lastName, state 만 놓고 보면, 주소와 관련된 변수라고 인식하기 어렵다.
- 그렇기 때문에 addrFirstName, addrLastName, addrState 와 같이 addr 이라는 접두어를 추가해서 맥락을 분명히 할 수 있다.
클래스를 통한 맥락 추가
- 때때로 맥락이 불분명한 메서드에 경우 클래스로 정의해서 맥락을 추가할 수 있다.
- 아래와 같은 메소드가 있다고 해보자.
private void printGuessStatistics(char candidate, int count) {
String number;
String verb;
String pluralModifier;
if (count == 0) {
number = "no";
verb = "are";
pluralModifier = "s";
} else if (count == 1) {
number = "1";
verb = "is";
pluralModifier = "";
} else {
number = Integer.toString(count);
verb = "are";
pluralModifier = "s";
}
String guessMessage = String.format("There %s %s %s%s", verb, number, candidate, pluralModifier);
}
- number, verb, pluralModifier 는 메서드의 전반에 걸쳐 사용된다.
- 하지만, number, verb, pluralModifier 는 함수를 끝까지 읽어봐야 의미가 파악된다.
- 이런경우 클래스를 추출해서 맥락을 부여할 수 있다.
public class GuessStatisticsMessage {
private String number;
private String verb;
private String pluralModifier;
public String make(char candidate, int count) {
createPluralDependentMessageParts(count);
return String.format("There %s %s %s%s", verb, number, candidate, pluralModifier);
}
private void createPluralDependentMessageParts(int count) {
if (count == 0) {
thereAreNoLetters();
} else if (count == 1) {
thereIsOneLetter();
} else {
thereAreManyLetters(count);
}
}
private void thereAreManyLetters(int count) {
number = Integer.toString(count);
verb = "are";
pluralModifier = "s";
}
private void thereIsOneLetter(){
number = "1";
verb = "is";
pluralModifier = "";
}
private void thereAreNoLetter(){
number = "no";
verb = "are";
pluralModifier = "s";
}
}
불필요한 맥락을 없애라
- Gas Station Deluxe 라는 애플리케이션을 짠다고해서, 모든 클래스 이름을 GSD 로 시작하는 것은 바람직하지 않다.
- 일반적으로 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해서다. 이름에 불필요한 맥락을 추가하지 않도록 주의해야 한다.
이외에 좋은 습관들
- 그릇된 정보를 피하라
- 발음하기 쉬운 이름을 사용하라
- 검색하기 쉬운 이름을 사용하라
- 인코딩을 피하라
- 자신의 기억력을 자랑하지 마라
- 기발한 이름은 피하라
- 말장난을 하지 마라
- 해법 영역에서 가져온 이름을 사용하라
- 문제 영역에서 가져온 이름을 사용하라
TDD 법칙 세 가지
- 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
- 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
- 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
TDD 원칙
- FIRST
- Fast 빠르게
- Independent 독립적으로
- Repeatable 반복가능하게
- Self-Validating 자가 검정하는
- Timly 적시에
REFERENCES