CleanCode 02 의미있는이름

소프트웨어 이름은 변수, 함수, 인수, 클래스, 패키지, 소스코드, 디렉터리, jar, war 등 여러 이름을 붙입니다. 이번 장에서는 이름을 잘 짓는 규칙을 알아보도록 하겠습니다.

1. 의도를 분명히 밝혀라

의도가 분명하게 이름을 지으라 라고는 말하기는 쉽지만 생각보다 이름을 짓는것은 쉽지 않다. 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 시간을 절약하는 시간이 훨씬 큽니다.

1
int d; // 경과시간(단위: 날짜)

위의 코드는 변수명 이름을 잘 지은것일까? 라는 의문이 들게됩니다. 위의 코드는 변수명에 아무런 의미도 드러나지 않고 경과시간이나 날짜라는 느낌이 전혀들지 않습니다.

그러면 어떻게 지어야할까요?

1
2
3
4
int elaspsedTimeInDays;
int daysSinceCreation;
int daysSinceModification
int fileAgeInDays;

다음과 같이 의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉬워집니다.

1
2
3
4
5
6
7
8
9
public List<int[]> getThem(){
List<int[]> list1 = new ArrayList<int[]>();
for(int[] x : theList){
if(x[0] == 4){
list1.add(x);
}
}
return list1;
}

위의 코드를 보면 코드가 하는일을 짐작하기 어렵습니다. 무슨 문제가 있는걸까요? 그렇다고 코드들이 복잡하다는것이 아닙니다.

문제는 무엇일까요?
문제는 코드의 단순성이 아니라 코드의 함축성입니다. 다시 말해, 코드 맥락이 코드자체에 명시적으로 드러나있지 않습니다. 위 코드는 암암리에 독자가 다음과 같은 정보를 안다고 가정을 합니다.

  1. theList 무슨값이?
  2. theList 0번째 값은 무엇때문에?
  3. 값 4는 무엇을 의미하지?
  4. 함수가 반환하는 list1은 무엇이지? 어떻게 사용하지?

이와 같은 성격이 드러나있지 않습니다.

자 그러면, 이제 지뢰찾기 게임을 만든다는 가정을 해보겠습니다.

theList : 게임판 : gameBoard로 바꾸어보겠습니다. 그러면 0번째값은 칸 상태를 뜻하고, 값 4는 깃발이 꽂힌 상태를 가리킨다.

1
2
3
4
5
6
7
8
9
public List<int[]> getFlaggedCells(){
List<int[]> flaggedCells = new ArrayList<int[]>();
for(int[] cell : gameBoard){
if(cell[STATUS_VALUE] == FLAGGED){
flaggedCells.add(cell);
}
}
return flaggedCells;
}

위의 코드를 변수만 변경해보았는데 단순성은 변하지 않고 들여쓰기도 같고, 코드는 더욱더 명확해졌습니다.
Int 배열을 사용하는 대신, 칸을 간단한 클래스로 만들어도 됩니다. isFlagged라는 더 명시적인 함수를 사용하여 상수를 감춰줘도 매우 괜찮은 결과가 될 것입니다.

1
2
3
4
5
6
7
8
9
public List<int[]> getFlaggedCells(){
List<Cell> flaggedCells = new ArrayList<int[]>();
for(Cell cell: gameBoard){
if(cell.isFlagged){
flaggedCells.add(cell);
}
}
return flaggedCells;
}

이름만 바꾸어 졌는데도 함수가 하는 일을 이해하기가 쉬워졌습니다. 깨끗한 코드가 주는 강력한 힘입니다.

2. 그릇된 정보를 피하라

널리쓰이는 의미 있는 단어를 다른의미로 사용하면 안됩니다. 예를 들면, hp, aix, sco 변수이름입니다.
hypotenuse(빗변) = hp 가 훌륭한 약어일지라도 독자들에게 그릇된 정보를 야기시킬 수 있습니다.
List라는 변수는 매우 특수한 의미를 가지고 있다. 여러 계정을 담는 리스트면 accountGroup, buchOfAccounts아니면 Accounts라 명명하는 식으로 처리합니다.

흡사한 이름을 버려라

XYZControllerForEfficientHandlingOfString, XYZControllerForEfficientStorageOfString 이라는 이름을 사용하면 두 단어는 매우 겁나 비슷하다.

유사한 표기법

일관성이 떨어지는 표기법은 그릇된 정보이다. 소문자 L은 1처럼보이고 대문자 O는 숫자0처럼 보인다.

1
2
3
4
5
6
int a = 1;
if(O == 1){
a = 01;
}else {
l = 01;
}

같은 변수를 실무에서 사용하는 코드가 실제로 있을 수 있다. 문서나 구전으로 미래 개발자 모두에게 알려야하는 해결책이고, 이름만 바꾸면 문제가 해결된다.

3. 의미 있게 구분하라

컴파일러나 인터프리터만 통과하려는 생각으로 코드를 구현하는 프로그래머는 스스로 문제를 일으킨다. 컴파일러를 통과할지라도 연속된 숫자를 덧붙이거나 불용어를 추가하는 방식은 적절하지 못한다.

a1,a2,a3…aN과 같은 의미를 띄는 이름을 지양해야한다.

1
2
3
4
5
public static void copy(char a1[], char a2[]){
for(int i=0; i<a.length; i++){
a2[i] = a1[i];
}
}

함수 인수 이름으로 source와 destination을 사용한다면 코드읽기가 더 쉬워집니다.
불용어를 추가한 이름 역시 아무런 정보도 제공하지 못합니다. Product라는 클래스가 있다고 가정하면 다른 클래스를 ProductInfo or ProductData라 부릅니다. Info나 Data는 a, an, the와 마찬가지로 의미가 불분명한 불용어이다.

접두어를 사용하지말라는것이 아니다. 의미가 분명히 다르다면 사용해도 무방하다.
지역변수 a를 사용하고 모든 함수 인수는 the를 사용하면 된다. zork라는 변수가 있다는 이유만으로 theZork라는 이름을 지어서는 안된다는 의미이다.

NameString VS Name 둘중 뭐가 더 나은 코드일까?

Name이 부동소수가 될 가능성이 있는지를 확인하고 그릇된 정보인지를 확인한다.

Customer VS CustomerObject

두개의 클래스가 있다고 가정하면 어떤 차이가 있는것 같아보이는가? 고객 급여 이력을 찾기위해서는 어떤것을 찾아야할까?

getActiveAccount(), getActiveAccounts, getActiveAccountInfo() 이 프로젝트에 참여한 프로그래머는 어떤 함수를 호출할지 어떻게 알까? 명확한 관례가 없다면 moneyAmount, money 는 구분이 되지 않는다.

즉, 읽는 사람이 차이를 알도록 이름을 지어라.

4. 발음하기 쉬운 이름을 사용하라

genymdhms(generate,date,year,month,day,hour,minute,sec-ond) 라고 발음한다는것을 가정해보자.
이게 발음하기 쉬운가? 어렵다 무슨말인지도 모르겠다.

1
2
3
4
5
6
7
8
9
10
class DtaRcrd102{
private Date genymdhms;
private Date modymdhms;
private final String pszpint = "102";
}
class Customer{
private Date generationTimestamp;
private Date modificationTimestamp;
private final String recordId = "102";
}

5. 검색하기 쉬운 이름을 사용하라

문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다는 문제점이 있습니다.

MAX_CLASSES_PER_STUDENT는 grep으로 찾기가 쉽지만, 숫자 7은 은근히 까다롭습니다. 7이 들어가는 파일 이름이나 수식이 모두 검색되기 때문입니다.

마찬가지로 e라는 문자도 변수 이름으로 적합하지 못합니다. 검색이 어렵기 때문입니다. e는 영어에서 가장 많이 쓰이는 문자이다.
이름 길이는 범위 크기에 비례해야합니다

1
2
3
4
5
6
7
8
9
10
11
12
13
for(int j=0; j<34; j++){
s+=(t[j]*4)/5;
}

int realDaysPerIdeaDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for(int j=0; j<NUMBER_OF_TASKS; j++){
int realTaskDays = taskEstimate[j] * realDaysPerIdeaDay;
int realTaskWeeks = (realTaskDays) / WORK_DAYS_PER_WEEK);
sum += realTaskWeeks;

}

만약 WORK_DAYS_PER_WEEK 매직넘버를 사용하지 않으면 이름을 모두 찾은 후 의미를 분석해 원하는 상수를 가려내야하는 번거로움이 발생한다.

6. 인코딩을 피하라

인코딩 정보는 매우 많으므로 개발자에게 인코딩은 불필요한 정신적 부담입니다. 따라서, 유형이나 범위정보까지 인코딩에 넣으면 해독하기가 어려워진다.

6.1. 헝가리식 표기법

변수에 부가 정보를 덧붙여 표기하지 마라.

1
PhoneNumber phoneString;

타입 시스템에서 의미없는 정보이다. 예전에는 컴파일러가 타입을 점검하지 않아 헝가리언 표기법으로 타입을 기억할 단서가 필요했다. 하지만 요즘은 컴파일러가 타입을 기억하고 강제한다.

6.2. 멤버 변수 접두어

멤버 변수에 _m이라는 접두어를 붙일 필요가 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Part{
private String m_dsc;
void setName(String name){
m_dsc = name;
}
}

public class Part{
String description;
void setDescription(String description){
this.description = description;
}
}

사람들은 접두어를 무시하고 이름을 해독하는 방식을 재빨리 익히고 코드를 읽을수록 접두어는 관심 밖으로 밀려나갑니다. 접두어를 쓰면 옛날코드를 의미하는게 되어버렸다.

6.3. 인터페이스 클래스와 구현 클래스

ABSTRACT FACTORY 구현시에 인터페이스 이름은 접두어를 붙이지 않는편이 좋다. IShapeFactory VS ShapeFactory와 같이 접두어를 써봤자 주의를 흐트리고 과도한 정보를 제공한다.

즉, 인터페이스명은 ShapeFactoryImp나 심지어 CShapeFactory or IShapeFactory보다 좋다.

7. 자신의 기억력을 자랑하지 마라

for문에서 i,j,k와 같은것은 괜찮지만 l이나 I와 같은 변수를 사용하면 안된다.
똑똑한 프로그래머와 전문가 프로그래머 사이에서 나타ㄴ는 차이점 하나만 들자면, 전문가 프로그래머는 명료함이 최고라는 사실을 안다.

8. 클래스 이름

클래스 이름과 객체이름은 명사나 명사구가 적합하다
Customer, WikePage, Account, AddressParser 와 같은것이 좋은 예이다
Manager, Processor,Data,Info와 같은 이름은 피하자

9. 메서드 이름

메서드 이름은 동사나 동사구가 적합하다. postPayment deletePage, save등이 좋은 예이고 Accessor, Mutator, Predicate는 Javabean표준에 따라 앞에 get,set,is를 붙인다

1
2
3
4
5
String name = employee.getName();
customer.setName("kgh");
if(paycheck.isPosted()){
....
}

생성자를 중복정의 할때는 정적 팩토리 메서드를 사용합니다.

1
2
3
4
// 좋은 코드 
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
// 나쁜코드
Complex fulcrumPoint = new Complex(23.0);

10. 기발한 이름은 피하라

HolyHandGrenade 함수가 무슨일을 하는지 알겠는가? 이것보단 DeleteItems가 더 좋다.
가끔, 예를 들어

kill()대신에 whack()이라 부르거나 Abort() 대신 eatMyShort()라 부릅니다. 농담을 피해라!

11. 한 개념에 한 단어만 사용하라

똑같은 메서드를 fetch, retrieve, get으로 제각각으로 부르면 혼란스럽습니다. 어느 클래스에서 어떤 이름을 썼는지 기억하기 어렵습니다. 이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르다고 생각합니다.
일관성있는 코드를 작성하자

12. 말 장난을 하지 마라

한 단어를 두가지 목적으로 사용하지 말라.
한 개념에 한 단어를 사용하라 라는 규칙을 따랐더니 여러 클래스에서 add라는 메서드가 생겼다. 모든 add메서드의 매개변수와 반환값이 의미적으로 똑같다면 문제가 없지만, 같은 맥락이 아닌데도 일관성을 고려해서 add라는 단어를 선택한다.

모두가 기존값을 두개를 더하거나 새로운 값을 만든다고 가정하면 이것을 add라고 쓰고, 하나의 값만 추가하는데도 add라는 말을 쓰면 말장난이다. 즉, 하나의 값을 더할때는 insert 나 append라는 이름이 적당하다.

13. 해법 영역에서 가져온 이름을 사용하라

모든 문제를 문제영역에서 가져오는 정책은 현명하지 못하다. 전산 용어, 알고리즘이름, 패턴 이름, 수학 용어등은 괜찮다. 프로그래머에게 익숙한 기술이 가장 적합한 선택이다

14. 문제 영역에서 가져온 이름을 사용하라

문제 영역개념과 관련이 깊은 코드라면 문제영역에서 이름을 가져와야한다

15. 의미 있는 맥락을 추가하라

스스로 의미가 분명한 의름이 없지 않다. 그래서 클래스 함수, 이름 공간에 넣어 맥락을 부여한다.

예를 들면, firstName, lastName, street, houseNumber, city, state, zipcode 라는 변수가 있으면 변수를 훑어보면 주소라는 사실을 알 수 있는데, state가 일부의 주소라는 사실을 알 수 있을까?

addr접두어를 추가하여 의미를 더욱 더 명확히 할 수 있다. addrFirstName, addrLastName, addrState라 쓰면 맥락이 조금더 분명해 진다. Address클래스를 사용하는것도 하나의 방법이다.

맥락이 불분명한 변수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 );

print(guessMessage);
}

맥락이 명확한 변수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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 thereAreNoLetters() {
number = "no";
verb = "are";
pluralModifier = "s";
}
}

16. 불필요한 맥락을 없애자

고급 휘발유 충전소(Gas Station Deluxe) 애플리케이션일 경우 모든 이름을 GSD라고 시작하면 안된다. 일반적으로 짧은 이름이 긴 이름보다 좋습니다. 의미가 분명한 경우 accountAddress, customerAddress는 맥락이 잘 나누어져 있다면 Address 로 충분하다.
포트주소, MAC 주소, 웹 주소를 구분해야 한다면 PostalAddress, MAC, URI 라는 이름도 좋다.

17. 마치면서

대다수 자신이 짠 클래스 이름 메서드 이름을 기억 못한다. 다른사람이 짠 코드를 손본다면 리팩터링 도구를 사용해서 이름을 개선해야합니다. 반대에 두려워하지 말고 코드개선 노력을 보이자!