시작하기에 앞서..
책에서 제시된 주제에 대한 이해와 의문점들을 제 나름대로 정리하여 설명하는 방식으로 글이 진행됩니다.
더 자세한 정보를 알고싶거나, 글을 보고 책에 관심이 생기셨다면 책을 구매하여 읽어보시는것을 추천드립니다.
제목 - Clean Code (클린 코드)
저자 - Robert C. Martin (로버트 C. 마틴)
저자는 "의미 있는 이름"이 중요하다고 말한다.
우리는 다양한 항목들(변수, 함수, 클래스 등)에 이름을 붙여야 하고, 그렇기에 우리는 좋은 이름을 만들어야 할 필요성이 있다.
저자는 "의미 있는 이름"을 만드는 방법을 다음과 같이 소개한다.
1. 의도를 분명히 밝혀라
2. 그릇된 정보를 피하라
3. 의미 있게 구분하라
4. 발음하기 쉬운 이름을 사용하라
5. 검색하기 쉬운 이름을 사용하라
6. 인코딩을 피하라
7. 자신의 기억력을 자랑하지 마라
8. 클래스 이름
9. 메서드 이름
10. 기발한 이름은 피하라
11. 한 개념에 한 단어를 사용하라
12. 말장난을 하지 마라
13. 해법 영역에서 가져온 이름을 사용하라
14. 문제 영역에서 가져온 이름을 사용하라
15. 의미 있는 맥락을 추가하라
16. 불필요한 맥락을 없애라
무려 열 하고도 여섯가지에 달하는 항목들이 존재한다.
자신이 위 항목들을 모두 숙지하고 있고, 개인 혹은 팀 프로젝트에 충분히 적용하고 있다면 다음 챕터로 넘어가도 좋다.
그러나, 그렇지 않다고 판단된다면 나와 같이 정리 해보도록 하자.
1. 의도를 분명히 밝혀라
저자는 "변수나 함수 그리고 클래스의 이름은 다음과 같은 질문에 모두 답해야 한다"라고 말한다.
이에 대한 질문은 다음과 같다.
1) 변수(혹은 함수나 클래스)의 존재 이유는 무엇인가
2) 변수의 수행 기능은 무엇인가
3) 변수의 사용 방법은 무엇인가
예시를 하나 들어보자.
private int d // 경과 시간 (단위: 날짜)
여기 변수 "d"가 존재한다. 그리고 우리는 이 친구로 위의 질문들에 모두 답을 해야한다.
변수 "d"의 이름을 참고하여 답해보자.
질문) 변수 "d"의 존재 이유는 무엇인가?
답) 알 수 없다.
질문) 변수 "d"의 수행 기능은 무엇인가?
답) 알 수 없다.
질문) 변수 "d"의 사용 방법은 무엇인가?
답) 알 수 없다.
물론 우리는 주석을 참고하여 대답할 수 있다.
그러나 잠시 생각해보자, 이름이 아닌 주석으로 변수의 의도를 파악해야 한다면 그것이 과연 좋은 이름인가?
적어도, 저자의 관점에서는 "아니다" 이다.
그렇다. 변수 "d"의 이름에서 우리는 어떠한 정보도 파악할 수 없다.
그렇다면 어떤 방법으로 수정해야 하는가?
저자는 "의도가 드러나는 이름을 사용하라"고 말한다.
그렇다면 "의도가 드러나는 이름"은 무엇인가?
다음 예시를 살펴보자.
private int elapsedTimeInDays;
private int daysSinceCreation;
private int daysSinceModification;
private int fileAgeInDays;
우리는 위 변수들의 이름에서 적어도 변수들이 경과 시간이나 날짜와 관련되어있다는 것을 알 수 있다.
이렇듯 의도가 드러나는 이름을 사용하면 코드의 이해와 변경이 쉬워진다.
어느정도 감이 잡히는가? 그렇다면 다음 함수를 살펴보자.
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<>();
for (int[] x : theList) {
if (x[0] == 4)
list1.add(x);
return list1;
}
다 살펴보았는가? 그러면 이제 위 함수가 어떤 일을 하는 친구인지 말할 수 있는가?
아마 아닐것이다. 그러나 다행히도, 우리는 무엇이 문제인지 알고있다.
저자는 이런 문제를 코드의 함축성이 부족하다고 말한다.
쉽게 말하자면, 코드의 맥락이 코드 자체에 명시적으로 드러나지 않는다는 말이다.
자, 그러면 지뢰찾기 게임을 만든다고 가정하고 위의 함수를 다음과 같이 변경해보자.
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<>();
for (Cell cell : gameBoard)
if (cell.isFlagged())
flaggedCells.add(cell);
return flaggedCells;
}
어떤 차이가 보이는가?
우선, 코드의 형태는 변하지 않았다. 그러나 더욱 명확해진것을 파악할 수 있다.
우리는 theList가 게임 판을 말한다는것을 알 수 있고, int[ ]은 게임 판의 각 칸에 해당한다는것을 알 수 있다.
그뿐인가? 게임 판의 각 칸을 int[ ] 대신 Cell 이라는 클래스로 만들고, 명시적인 함수를 제작하여 사용함으로서 더욱 개선된 모습을 보여주고있다.
그러면 다시 한번 말해보자. 위 함수는 어떤 일을 하는가?
2. 그릇된 정보를 피하라
저자는 "프로그래머는 코드에 그릇된 단서를 남겨서는 안된다" 라고 말한다. 그릇된 단서는 코드의 의미를 흐리기 때문이다.
저자가 말하는 그릇된 단서를 피하는 방법은 다음과 같다.
1) 널리 쓰이는 의미가 있는 단어는 다른 의미로 사용해서는 안된다.
대표적인 예로, hp, aix, sco는 변수 이름으로 적합하지 않다. 유닉스 플랫폼이나 유닉스 변종을 가르키는 이름이기 때문이다.
마찬가지로, 실제 자료구조가 아니라면 List, Set등의 이름(userList, userSet)도 피하는것이 좋다.
2) 서로 흡사한 이름을 사용하지 않도록 주의한다.
간단하다. 여기 두가지 클래스가 있다. 클래스의 이름을 한눈에 구분할 수 있는가? 쉽지 않을것이다.
public class XYZControllerForEfficientHandlingOfStrings { }
public class XYZControllerForEfficientStorageOfStrings { }
3) 유사한 개념은 유사한 표기법을 사용한다.
저자는 "유사한 개념은 유사한 표기법을 사용하는것 또한 정보" 라고 말한다.
대표적인 예로, 사용하는 IDE(통합 개발 환경, Intergrated Development Enviroment)의 자동 완성 기능을 살펴보자.
자동 완성 후보 목록에 유사한 개념이 알파벳 순으로 나온다면, 그리고 각 개념의 차이가 명백히 드러난다면 어떠한가?
매우 유용하게 사용할 수 있을것이다.
3. 의미 있게 구분하라
저자는 "이름이 달라야 한다면 의미도 달라져야 한다" 라고 말한다.
다음 항목들을 살펴보자.
1) 연속된 숫자를 덧붙이는 방법은 피하라
아래 변수의 집합들을 살펴보자.
private int N1, N2, N3, N4, N5;
위 변수들은 N이라는 문자에 단순히 숫자를 덧붙여 작명되어있다.
우리는 위 변수들의 이름에서 어떤 정보들을 제공받을 수 있는가?
아마 없을것이다. 위 변수들은 이름에서 저자의 의도가 전혀 드러나지 않는다.
간단한 예시를 살펴보자.
public static void copyChar(char[] a1, char[] a2) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
public static void copyChar(char[] source, char[] destination) {
for (int i = 0; i < source.length; i++) {
destination[i] = source[i];
}
}
단순히 a1을 source로, a2를 destination으로 변경했을 뿐이지만, 코드 읽기가 훨씬 쉬워진것을 확인할 수 있다.
2) 불용어의 사용을 피하라
* 불용어 (noise word, 저자는 "개념을 구분하지 않은 채 이름만 달리하는 단어"라는 의미로 사용함)
몇가지 예시를 살펴보자.
moneyAmount와 money는 어떤 차이가 있는가?
theMessage는 Message와 어떤 차이가 있는가?
accountData는 account와 어떤 차이가 있는가?
전혀 구분이 되지 않는다.
그렇기에, 저자는 "읽는 사람이 차이를 알 수 있도록 이름을 지어라"라고 말한다.
4. 발음하기 쉬운 이름을 사용하라
저자는 "프로그래밍은 사회 활동이다" 라고 말한다.
만약 당신이 팀원과 토론을 한다고 가정해보자.
그리고 당신의 눈앞에 genymdhms(generate date, year, month, year, day, hour, minute, sceond) 라는 변수가 존재한다.
당신은 이 변수를 어떻게 읽을것인가? 젠 와이 엠 디 에이취 엠 에스? 쉽지않다.
그렇기에 우리는 발음하기 쉬운 이름을 필요로한다.
5.검색하기 쉬운 이름을 사용하라
저자는 "이름 길이는 범위 크기에 비례해야 한다" 라고 말한다.
다음 두가지 코드를 비교해보자.
public void someMethod() {
for (int j = 0; j < 34; j++) {
s += (t[j] * 4) / 5;
}
}
public void anotherMethod() {
int realDaysPerIdealDay = 4;
final int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
int realTaskDays = taskEstimate[j] * readDaysPerIdealDay;
int realTaskWeeks = readTaskDays / WORK_DAYS_PER_WEEK;
sum += readTaskWeeks;
}
}
첫번째 코드에서 t를 검색한다고 가정해보자. 프로젝트의 크기가 작다면 문제 없을지도 모른다.
그러나 규모가 크다면? t를 포함하고 있는 단어가 몇가지가 되겠는가. 검색하기 쉽지 않을것이다.
위와 같은 관점에서, 두번째 코드의 taskEstimate와 같이 긴 이름이 첫번째 코드의 t과 같은 짧은 이름보다 좋다.
상수의 경우에서도, 첫번째 코드의 5가 무엇을 나타내는지 알 수 있는가? 설계한 직후에는 바로 알 수 있을지도 모른다.
그러나 몇 개월 후, 아니면 당장 몇 일 후에라도 다시 코드를 살펴보자.
그래도 상수 5가 무엇을 나타내는지 알 수 있는가?
그러나 두번째 코드처럼, 숫자 5를 WORK_DAYS_PER_WEEK라고 명명하여 사용해보자.
오히려 잊어버리기가 쉽지 않을 것이다.
6. 인코딩을 피하라
* 인코딩이란?
변수나 함수의 이름에 데이터의 타입이나 상태를 명시하는 것을 말한다.
현재는 대부분의 경우에서 더 이상 인코딩을 사용하지 않는다.
인코딩 대신 IDE에서 지원하는 기능을 적극적으로 사용하도록 하자.
대표적인 인코딩 방법은 다음과 같다.
1) 헝가리안 표기법
2) 멤버 변수 접두어
예외적으로 인코딩을 사용하는 경우가 존재하는데, 이는 다음과 같다.
1) 인터페이스 클래스와 구현 클래스
예를들어, 도형을 생성하는 Factory를 구현한다고 가정해보자.
해당 팩토리는 인터페이스 클래스이고, 구현은 구체 클래스에서 한다.
그렇다면 두 클래스의 이름을 어떻게 작명하는것이 좋은가?
저자는 "인터페이스 이름에는 접두어를 붙이지 않는 것이 좋다."라고 말한다.
옛날 코드에서 많이 사용하는 접두어 I는 프로그래머의 주의를 흐트리거나 과도한 정보를 제공한다.
따라서, 저자는 굳이 접두어를 사용해야 할 상황이 발생한다면
인터페이스 IShapeFactory와 구체 클래스 ShapeFactory 보다, 인터페이스 ShapeFactory와 구체 클래스 ShapeFactoryImpl 혹은 ShapeFactoryImp 심지어는 CShapeFactory가 오히려 낫다고 말한다.
7. 자신의 기억력을 자랑하지 마라