[클린코드] 2. 의미 있는 이름
2장 의미 있는 이름
의도를 분명히 밝혀라
- 변수(혹은 함수나 클래스)의 존재 이ㅠ는?
- 수행 기능은?
- 사용 방법은?
이러한 질문들에 따로 주석이 필요치 않게 이름을 지어야 한다. - ex1)
int d; // 경과 시간(단위: 날짜)
- 대신 다음과 같이 쓰는 것이 조금 더 명확하다.
```False
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
``` - ex2\)
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`는
게임판. 값 4는 깃발이 꽂힌 상태. 배열에서 0번째 값은
칸 상태)
```False
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라는 명시적 함수를 이용해서 FLAGGED라는
상수를 감출 수 있다.
```False
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<Cell>();
for (Cell cell : gameBoard)
if (cell.isFlagged())
flaggedCells.add(cell);
return flaggedCells;
}
```
그릇된 정보를 피하라
- 널리 쓰이는 의미가 있는 단어를 다른 의미로 쓰지 말라
- ex) hp, aix, sco는 유닉스 플랫폼이나 유닉스 변종을 가리키는 이름이기 때문에 이를 사용하면 혼동을 줄 수 있다.
- 특별한 의미가 있는 단어를 그 의미가 없는데 사용하지 말아라
- 프로그래머에게 List는 자료 구조에서 중요한 의미를 갖는다.
- 계정을 담는 컨테이너가 실제 List가 아닌데
accountList라는 이름을 사용하는 것은 잘못된 정보를
전달하는 것
- accountGroup, bunchOfAccounts, Accounts등으로 고치자
- 서로 흡사한 이름을 사용하지 말아라
- XYZControllerForEfficientHandlingOfStrings와 XYZControllerForEfficientStorageOfStrings
- 헷갈리게 하지 말자
- 유사한 개념은 유사한 표기법을 사용한다.(일관성)
- IDE는 코드 자동 완성 기능을 제공한다. 여기서 뜨는 후보 목록에 유사한 개념이 뜨고, 그 개념 차이가 명백히 드러난다면 코드 자동 완성 기능은 유용해진다.
- 즉 일관성을 가지면서 구분이 명확히 될 수 있는 이름을 짓자.
- 구분되기 힘든 알파벳 사용에 주의하자.
- ex) 소문자 L이나 대문자 O 변수
int a = l; if ( O == l ) a = O1; else l = 01;
의미 있게 구분하라
- 다른 두 개념에 같은 이름을 붙이면 동일한 scope내에서는
컴파일 되지 않는다.
- 그냥 연속적인 숫자를 덧붙이거나 불용어(noise word)를 붙여서 컴파일 되게 넘어가고 싶은 생각이 들 수 있다.
- 그러면 컴파일은 되겠지만 이름이 아무 정보를 주지 못하기 때문에 후에 유지, 보수에 어려움을 겪게 될 것이다.
- 연속적인 숫자를 덧붙인 이름(a1, a2, … , aN)은 의도적인
이름과 정반대이다.
- ex)
public static void copyChars(char a1[], char a2[]) { for (int i = 0; i < a1.length; i++) { a2[i] = a1[i]; } }
- 함수 인수 이름으로 source와 destination을 사용하면 코드 읽기가 훨씬 더 쉬워질 것.
- 불용어를 추가한 이름도 마찬가지
Product
라는 클래스가 있을 때 다른 클래스를ProductInfo
또는ProductData
라고 부르면 개념을 구분하지 않은 채 이름만 바꾼 것이라 할 수 있다.- Info, Data는 a, an, the와 마찬가지로 의미가 불분명한 불용어이다.
- a, the 같은 접두어를 사용하지 말라는 것이 아니라
의미가 분명히 다를 때만 사용하라는 것이다.
- ex) 모든 지역 변수는 a를 사용하고 모든 함수 인수는
the를 사용하기로 한 경우는 명확히 구분된다.
- 하지만 zork라는 변수가 있다는 이유만으로 theZork라고 이름 지어서는 안된다는 말이다.
- ex) 모든 지역 변수는 a를 사용하고 모든 함수 인수는
the를 사용하기로 한 경우는 명확히 구분된다.
- 불용어는 중복이다.
- 변수 이름에 variable이라는 단어는 쓸데없다.
- 표 이름에 table이라는 단어도 마찬가지.
- NameString에서 Name은 당연히 String일 것이므로 String은 쓸데없다.
- 코드에 Customer 클래스와 CustomerObject 클래스를 발견한다면 대체 무슨 차이를 찾을 수 있을까
- ex)
getActiveAccount(); getActiveAccounts(); getActiveAccountInfo();
- 세 개의 함수가 있다면 대체 뭘 호출해야 할지 구분할 수 있을까 - moneyAmount는 money와 구분이 되지 않는다. - customerInfo는 customer와, accountData는 account와, theMessage는 message와 구분이 되지 않는다.
발음하기 쉬운 이름을 사용하라
- 발음하기 쉬운 단어가 기억하기도 쉽고 소통하기도 쉽다.
- ex)
genymdhms
(generate date, year, month, day, hour, minute, second)라는 단어는 발음하기 어렵.
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszquint = "102";
/* ... */
};
- 위의 코드보다는 아래 코드가 더 이해하고 소통하기 쉬울
것이다.
```False
class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;
private fianl String recordId = "102";
/* ... */
};
```
검색하기 쉬운 이름을 사용하라
- 문자 하나만 사용하는 이름이나 상수는 텍스트 코드에서 쉽게
찾기 어렵다.
- ex) grep으로
MAX_CLASSES_PER_STUDENT
를 찾는 것과 7을 찾는 것을 생각해보면 된다.- 7이 들어가는 파일 이름이나 수식이 모조리 검색될 것이다.
- 검색으로 찾았더라도 7을 사용한 의도가 다를 수도 있다.
- 상수가 여러 자리 숫자이고 누군가 상수 내 숫자
위치를 바꿨다면 상수에 버그가 있지만 검색으로 찾지
못한다.
1234567
을1234576
으로 바꾸면 분명 있는데 코드에서는 오류가 나는 상황..?(확실치 않음)
- ex2)
e
도 문자 변수 이름으로 적합하지 않음
- ex) grep으로
- 이름 길이는 범위 크기에 비례해야 한다.
- 간단한 메소드에서 로컬 변수만 한 문자를 사용한다.
- 변수나 상수를 코드 여러 곳에서 사용한다면 검색하기
쉬운 이름이 바람직하다.
- (추측) 여러 곳에서 사용될 수록 코드를 고칠 때 해당
변수나 상수를 찾아서 고쳐야 될 때 검색을 통해 다른
변수나 상수와 쉽게 구분될 수 있어야 하기 때문?
- 범위가 작으면 검색 없이 범위 안에서 직접 찾을 수 있다.
- (추측) 여러 곳에서 사용될 수록 코드를 고칠 때 해당
변수나 상수를 찾아서 고쳐야 될 때 검색을 통해 다른
변수나 상수와 쉽게 구분될 수 있어야 하기 때문?
- ex)
for (int j=0; j<34; j++) { s += (t[j]*4)/5; }
- 위의 코드보다 아래 코드가 적합 ```False int realDaysPerIdealDay = 4; const int WORK_DAYS_PER_WEEK = 5; int sum = 0; for (int j=0; j < NUMBER_OF_TASKS; j++) { int realTaskDays = taskEstimate[j] * realDaysPerIdealDay; int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK); sum += realTaskWeeks; } ```
인코딩을 피하라
- 굳이 추가하지 않아도 이름에 인코딩할 정보는 많다.
- 유형이나 범위 정보까지 인코딩에 넣으면 해독하기 어려워진다. ### 헝가리식 표기법
- 이름 길이가 제한된 언어를 사용하던 옛날에 어쩔 수 없이
사용하던 방법
- 타입을 변수명에 넣어주는 것
- 요즘은 컴파일러가 타입을 체크 및 강제해주고, 클래스와 함수는 점차 작아져서 변수를 선언한 위치와 사용하는 위치가 멀지 않으므로(타입을 쉽게 알 수 있으므로) 굳이 쓸 이유가 없다.
- 특히 자바는 강타입이므로 더더욱..
- 오히려 변수, 함수, 클래스 이름이나 타입을 바꾸기 어려워지고 읽기도 어려워진다. 또 독자를 오도할 가능성도 커진다.
PhoneNumber phoneString;
// 타입이 바뀌어도 이름은 바뀌지 않는다.
멤버 변수 접두어
- 멤버 변수에
m_
이라는 접두어를 붙이는 것. - 이제는 그럴 필요가 없다.
- 클래스와 함수는 접두어가 필요없을 정도로 작아야 마땅하다.
- 또한 멤버 변수를 다른 색상으로 표시하거나 눈에 띄게 보여주는 IDE를 사용해야 마땅하다.
- ex)
public class Part {
private String m_dsc; // 설명 문자열
void setName(String name) {
m_dsc = name;
}
}
- 대신 다음이 더 적합
```False
public class Part {
String description;
void setDescription(String description) {
this.description = description;
}
}
``` - 또 사람들이 접두어(또는 접미어)에 익숙해지고 해당 정보는 자연스레 무시하게 되므로 더더욱 의미가 없어진다. ### 인터페이스 클래스와 구현 클래스 - 인코딩이 필요한 경우도 있다.
- 예를 들어 도형 생성하는 '추상 팩토리'를
구현한다고 가정.
- 해당 팩토리는 인터페이스 클래스로 하고, 구현은
구체 클래스(concrete class)에서 한다.
- 그러면 각각을 `IShapeFactory`와
`ShapeFactory`로 네이밍?
- 저자 의견은 인터페이스에 접두어를 붙이지 않는 방향
- 굳이 인터페이스 클래스 이름과 구현 클래스 이름
중 하나를 인코딩해야 한다면 구현 클래스 이름을
선택
- `ShapeFactoryImp`나
`CShapeFactory` 같은 형식
자신의 기억력을 자랑하지 마라
- 자신만 알 수 있는 모호한 변수를 사용하지 말자
- 보통 문자 하나만 사용하는 변수는 문제가 있다.
- 루프에서 반복 횟수를 세는 변수 i, j, k정도만 전통적으로 용인되는 범위
- 명료함이 최고다
클래스 이름
- 클래스 이름과 객체 이름은 명사나 명사구가 적합하다.
- ex) Customer, WikiPage, Account, AddressParser
- Manager, Processor, Data, Info 등과 같은 단어는 피하고, 동사는 사용하지 않는다.
메소드 이름
- 동사나 동사구가 적합.
- ex) postPayment, deletePage, save
- 접근자(Accessor), 변경자(Mutator), 조건자(Predicate)는 javabean 표준에 따라 값 앖에 get, set, is를 붙인다.
string name = employee.getName();
customer.setName("mike");
if (paycheck.isPosted())...
- 생성자(Constructor)를 중복정의(overload)할 때는 정적 팩토리 메소드를 사용
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
- 아래 코드보다는 위의 코드가 낫다.
```False
Complex fulcrumPoint = new Complex(23.0);
```
- 만약 여기서 생성자 사용을 제한하고 싶다면 해당
생성자를 private으로 선언하자.
기발한 이름은 피하라
- 괜히 농담 쓰는 것보다 명료한 이름을 쓰자.
한 개념에 한 단어를 사용하라
- 추상적인 개념 하나에 단어 하나를 선택해서 이를 고수하자
- 예를 들어서 똑같은 메소드를 클래스마다 fetch, retrieve, get으로 제각기 부르면 혼란스럽다.
- 그렇게 되면 어떤 용어를 썼는지 알기 위해 라이브러리를 작성한 회사나 그룹이나 개인을 기억해야 한다.
- 이클립스, 인텔리제이 등에서는 문맥에 맞는 단서를 제공.
- 예를 들어 객체를 사용하면 객체가 제공하는 메소드 목록을 보여주지만 목록은 보통 함수 이름과 매개변수만 보여주고 주석은 보여주지 않는다. 운이 좋아야 매개변수 이름을 보여준다.
- 그러니 메소드 이름은 독자적이고 일관적이으로 지어야 프로그래머가 주석을 뒤지지 않고도 올바른 메소드를 선택할 수 있다.
- 마찬가지로 동일 코드 기반에 controller, manager, driver를 섞어 쓰면 DeviceManager와 ProtocolController는 근본적으로 어떻게 다른지 설명하기 어려워진다.
말장난을 하지 마라
- 한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용하면 그것은 말장난에 불과하다.
- “한 개념에 한 단어를 사용하라”는 규칙을 따르니
여러 클래스에 add라는 메소드가 생겼다.
- 모든 add 메소드의 매개변수와 반환값이 의미적으로 똑같으면 문제가 없다.
- 하지만 같은 맥락이 아닌데 ‘일관성’을
고려한답시고 add라는 단어를 선택하면 다른 개념에 같은
단어를 사용한 것이 될 수 있다.
- 기존의 add 메소드는 모두 기존 값 두 개를 더하거나 이어서 새로운 값을 만드는데, 새로 작성하는 메소드는 집합에 값 하나를 추가한다면 기존 add 메소드와 맥락이 다르므로 insert나 append라는 이름이 적당하다.
해법 영역에서 가져온 이름을 사용하라
- 코드를 읽을 사람도 프로그래머다.
- 그러니 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 좋다.
- 모든 이름을 문제 영역에서 가져오는 정책은 같은 개념을 다른 이름으로 이해하던 동료들이 매번 고객에게 의미를 물어야 하기 때문에 좋지 못하다.
- VISITOR 패턴에 친숙한 프로그래머는 AccountVisitor라는 이름을 금방 이해하고, JobQueue는 모든 프로그래머가 알 것이다.
- 기술 개념에는 기술 이름이 가장 적합하다.
문제 영역에서 가져온 이름을 사용하라
- 적절한 ‘프로그래머 용어’가 없다면 문제 영역에서
이름을 가져와라.
- 그리 하면 코드를 유지/보수하는 프로그래머가 분야 전문가에게 의미를 물어서 파악할 수 있다.
- 우수한 프로그래머와 설계자라면 해법 영역과 문제 영역을 잘 구분해서 문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 한다.
의미 있는 맥락을 추가하라
- 스스로 의미가 분명한 이름이 있긴 하지만 대다수 이름은 그렇지 못하다.
- 그래서 클래스, 함수, 이름 공간 안에 속하도록 해서 맥락을 부여한다.
-
그것조차 실패하면 마지막 수단으로 접두어를 붙인다.
- 예를 들어 firstName, lastName, street, houseNumber, city, state, zipcode라는 변수가 있다면 훑어보면 state가 주소라는 사실을 알아챌 수 있다.
- 하지만 어느 메소드가 state라는 변수 하나만 사용한다면 변수 state가 주소 일부라는 사실을 금방 알아채기 어렵다.
- 그런 경우 addr라는 접두어를 추가해서 addrFirstName,
addrLastName, addrState라 쓰면 맥락이 조금 더
분명해진다.
- 적어도 변수가 조금 더 큰 구조에 속한다는 사실이 명확해진다.
- 물론 Address라는 클래스를 생성하면 더 좋다.
- 그러면 변수가 조금 더 큰 개념에 속한다는 사실이 컴파일러에도 분명해진다.
-
ex)
- 다음 예시에서 변수에 대한 맥락은 함수 이름이 일부분만 제공하고 나머지는 알고리즘이 제공한다. 따라서 함수를 끝까지 읽어봐야 number, verb, pluralModifier라는 변수 세 개가 ‘통계 추측’ 메시지에 사용된다는 것을 알 수 있다. 이는 독자가 맥락을 유추해야만 한다.
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); }
- 위의 메소드는 일단 좀 길고, 세 변수를 함수 전반에서 사용한다. - 대신 GuessStatisticsMessage라는 클래스를 만들고 세 변수를 클래스에 넣으면 맥락이 분명해진다. - 즉, 세 변수는 확실히 GuessStaticsMessage에 속한다. - 이렇게 맥락을 개선하면 함수를 쪼개기 쉬워지므로 알고리즘도 조금 더 명확해진다. ```False 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 thereAreNoLetters() { number = "no"; verb = "are"; pluralModifier = "s"; } } ```
불필요한 맥락을 없애라
- 예를 들어 ‘고급 휘발유 충전소(Gas Station
Deluxe)’라는 애플리케이션을 짠다 했을 때 모든 클래스
이름을 GSD로 시작하겠다는 생각은 바람직하지 않다.
- 오히려 IDE에서 G를 입력하고 자동 완성 키를 누르면 IDE는 모든 클래스를 열거한다.
- 또 비슷하게 GSD 회계 모듈에 MailingAddress 클래스를
추가하면서 GSDAccountAddress로 이름을 바꿨다고 하자.
- 나중에 다른 고객 관리 프로그램에서 고객 주소가 필요하면 GSDAccountAddress클래스는 부적절한 이름이 된다.
- 의미가 분명한 경우에 한해서 짧은 이름이 긴 이름보다 좋다. 이름에 불필요한 맥락을 추가하지 않도록 주의하자.
- accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이지만 클래스 이름으로는 적합하지 못하다.
- Address는 클래스 이름으로 적합하다.
- 포트 주소, MAC 주소, 웹 주소를 구분해야 한다면 PostalAddress, MAC, URI가 의미가 조금 더 분명할 것이다.
마치면서
- 좋은 이름을 선택하려면 설명 능력이 뛰어나야 하고 문화적인 배경이 같아야 한다.
- 또 다른 개발자가 반대할까봐 이름을 바꾸지 않으려고
생각하는 사람들이 많지만 조금 다르게 생각해보자.
- 어차피 암기는 컴퓨터가 하는 것이고 프로그래머는 코드를 짜는 데만 집중해야 한다.