혼자 정리
2. 의미 있는 이름 본문
2장 의미 있는 이름
의도를 분명히 밝혀라
- 변수(혹은 함수나 클래스)의 존재 이ㅠ는?
- 수행 기능은?
- 사용 방법은?
이러한 질문들에 따로 주석이 필요치 않게 이름을 지어야 한다. - ex1)
int d; // 경과 시간(단위: 날짜)
- 대신 다음과 같이 쓰는 것이 조금 더 명확하다.
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; }
- 위의 코드는 독자가 다음 정보를 안다고 가정.
theList
에 무엇이 들었는지?theList
에서 0번째 값이 어째서 중요한가?- 값 4는 무슨 의미인가?
- 함수가 반환하는 리스트
list1
을 어떻게 사용하는가?
- 이러한 정보를 코드에 직접 담는 것이 더 바람직하다.
- (위의 코드는 사실 지뢰찾기.
theList
는 게임판. 값 4는 깃발이 꽂힌 상태. 배열에서 0번째 값은 칸 상태)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라는 상수를 감출 수 있다.
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;
- ex) 소문자 L이나 대문자 O 변수
의미 있게 구분하라
- 다른 두 개념에 같은 이름을 붙이면 동일한 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을 사용하면 코드 읽기가 훨씬 더 쉬워질 것.
- ex)
- 불용어를 추가한 이름도 마찬가지
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"; /* ... */ };
- 위의 코드보다는 아래 코드가 더 이해하고 소통하기 쉬울 것이다.
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; }
- 위의 코드보다 아래 코드가 적합
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; } }
- 대신 다음이 더 적합
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);
- 아래 코드보다는 위의 코드가 낫다.
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에 속한다.
이렇게 맥락을 개선하면 함수를 쪼개기 쉬워지므로 알고리즘도 조금 더 명확해진다.
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가 의미가 조금 더 분명할 것이다.
마치면서
- 좋은 이름을 선택하려면 설명 능력이 뛰어나야 하고 문화적인 배경이 같아야 한다.
- 또 다른 개발자가 반대할까봐 이름을 바꾸지 않으려고 생각하는 사람들이 많지만 조금 다르게 생각해보자.
- 어차피 암기는 컴퓨터가 하는 것이고 프로그래머는 코드를 짜는 데만 집중해야 한다.