[CleanCode] 01. 의미있는 이름을 지어라
01 Sep 2022의미있는 이름을 지어라
소프트웨어에서 이름은 어디나 쓰인다. 변수, 함수, 클래스 인수등 모든 사용처에는 이름을 입력한다. 어찌보면 간단한 규칙이지만 어떻게 이름을 짓느냐에 따라 일의 효율이 달라지기도 한다. 몇가지 명명법에 대한 규칙을 소개하고자 한다.
의도를 분명히 밝혀라.
변수나 함수 그리고 클래스 이름은 다음과 같은 굵직한 질문에 모두 답해야 한다. 변수(혹은 함수나 클래스)의 존재 이유는? 수행 기능은? 사용 방법은? 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이다.
int d // 경과 시간 (단위: 날짜)
이름 d는 아무 의미도 드러나지 않는다. 경과 시간이나 날짜라는 느낌이 안든다. 측정하려는 값과 단위를 표현하는 이름이 필요하다.
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
이렇게 d가 아닌 각각 의도가 드러나는 이름을 사용한다면 코드의 이해와 변경이 쉬워진다.
하나더 예시를 들어보도록 하자.
아래의 코드는 지회찾기 게임에서 동작하는 로직을 구현한 것이다.
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<int[]>();
for (int[] x : theList)
if (x[0] == 4)
list1.add(x);
return list1;
}
위의 소스는 돌아가는 동작을 이해하는것은 쉬울순 있어도, 해당 코드를 통해 어떤 일을 하는것인지는 짐작하기 어렵다.
문제의 코드는 단순성이 아닌 코드의 함축성이다. 각각 의미있는 변수명을 이용해 코드를 작성해 보도록 하자. 각 개념에 이름만 붙여도 코드가 상당히 나아진다.
public List<int[]> getFlaggedCells() {
List<int[]> falggedCells = new ArrayList<int[]>();
for (int[] cell : gameBoard)
if (cell[STATUS_VALUE] == FLAGGED)
falggedCells.add(cell);
return falggedCells;
}
코드의 단순성은 변하지 않았지만, 코드는 더욱 명확해진것을 볼 수 있다.
더 나아가 int배열의 cell 필드를 칸에 대한 간단한 클래스로 만들면 더욱 객체지향적으로 코드를 작성 할 수 있다.
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<Cell>();
for (Cell cell : gameBoard)
if (cell.isFlagged())
flaggedCells.add(cell);
return flaggedCells;
}
단순히 이름만 고쳤을 뿐인데도 함수가 하는일을 이해하기가 쉬워졌다. 바로 이것이 좋은 이름이 주는 위력이다.
그릇된 정보를 피하라
프로그래머는 코드에 그릇된 단서를 남겨서는 안된다. 그릇된 단어는 코드의 의미를 흐리기 때문이다. 나름대로 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해도 안된다. 예를 들어, hp, aix, sco는 변수 이름으로 적합하지 않다. 유닉스 플랫폼이나 유닉스 변종을 가리키는 이름이기 때문이다.
또하나 예시를 들자면, 실제 필드가 List가 아니라면, accountList라 명명하는 것은 옳지 않다. 프로그래머에게 List라는 단어는 특수한 의미이며, 계정을 담는 컨테이너가 실제 List가 아니라면, 프로그래머에게 그릇된 정보를 제공하는 셈이다. 그러므로 accountGroup, bunchOfAccounts, 아니면 단순히 Accounts라 명명한다.
의미있게 구분하라
컴파일러나 인터프리터만 통과하려는 생각으로 코ㅓ드를 구현하는 프로그래머는 스스로 문제를 일으킬 수있다. 예를 들어 연속적인 숫자를 덧붙인 이름 (a1, a2, …, aN)은 의도적인 이름과는 정반대이다. 아무런 의미를 전달 하지 않기 때문에 사용하기에 옳지 않는 방법이다.
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
a, is, the 와 같은 아무런 의미를 전달하지 않는 불용어를 붙이는것도 크게 도움이 되지 않는다. zork라는 변수가 있다는 이유만으로 theZork라는 이름을 지어서는 안 된다는 말이다.
이와 같은 오류를 저지르는 애플리케이션이 있다. 개발자를 보호하고자 이름을 바꿨으나 오류 형태는 정확히 다음과 같다.
getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();
이 프로젝트에 참여한 프로그래머는 어느 함수를 호출할지 어떻게 알까? 읽는 사람이 차이를 쉽게 구분할 수 있도록 이름을 짓도록 하자.
발음하기 쉬운 이름을 사용하라.
발음이 어려운 이름은 토론하기 어렵다 예를 들면 ‘genymdhms’ 라는 필드명을 가지고 희의를 진행한다고하면, 해당 필드에 대해 언급을 하기 어렵지 않은가?
class DtaRcrd102 {
private Date genymdhms;
private Date modymdhms;
private final String pszqint = "102";
/* ... */
}
class Customer {
private Date generationTimestamp;
private Date modificationTimestamp;
private final String recordID = "102";
/* ... */
}
첫째와 둘째의 코드를 간단하게 비교를 한다 해도, 둘째 코드를 이용해서는 지적인 대화가 가능해진다.
검색하기 쉬운 이름을 사용하라
문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다는 문제점이 있다.
MAX)CLASSES_PER_STUDENT
는 grep으로 찾기가 쉽지만 단순한 7이라는 숫자는 검색하기가 쉽지않다. 7이 들어가는 파일명이나 이름 , 수식어가 모두 검색되 때문이다. 검색은 되었지만 찾고자 하는 필드가 아닌 다른 의도로 사용되는 경우도 있다.
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;
}
두가지의 코드를 비교한다 해도, 후자의 경우가 검색을 할 때 쉽게 찾을 수 있는 코드이다.만약 WORK_DAYS_PER_WEEK가 아닌 그냥 5를 사용한다면 5가 들어가는 이름을 모두 찾은 후 의미를 분석해 원하는 상수를 가려내야 한다.
인코딩을 피하라
굳이 부담을 더하지 않아도 이름에 인코딩할 정보는아주 많다. 유형이나 범위 정보까지 인코딩에 넣으면 그만큼 이름을 해독하기 어려워진다. 일ㄴ코딩한 이름은 거의가 밞음하기 어려우며 오타가 생기기 쉽다.
자신의 기억력을 자랑하지 마라
독자가 코드를 읽으면서 변수 이름을 자신이 아는 이름으로 변환해야 한다면 그 변수 이름은 바람직하지 못하다. 이는 일반적으로 문제 영역이나 해법 영역에서 사용하지 않는 이름을 선택했기 때문에 생기는 문제다.
문자 하나만 사용하는 변수이름은 문제가 있다. 루프에서 반복 횟수를 세븐 변수 i, j, k 등등은 괜찮지만 루프 범위가 아주 작고 다른 이름과 충돌 하지 않을 때만 사용해야 한다.
그외에는 대부분 적절하지 못하다. 변수 a 와 b를 사용할때 이것이 어떤 의미로 변수명을 a와 b로 구성하였는지 끝까지 기억할 자신이 있는가?
클래스 이름
클래스 이름과 객체 이름은 명사나 명사구가 적합하다. Customer, WikiaPage, Account, AddressParser 등이 좋은 예시이며, Manager, Processor, Data, Info 등과 같은 단어는 피하고, 동사는 사용하지 않는다.
메서드 이름
메서드 이름은 동사나 동사구가 적합하다. postPayment, deletePage, save 등이 좋은 예시이다. 필드의 접근자, 변경자, 조건자 경우 javabean 표준에 따라 값 앞에 get, set, is를 붙인다.
생성자(Constructor)를 중복정의(Overload)할 때의 정적 팩토리 메서드를 사용한다. 메서드는 인수를 설명하는 이름을 사용한다.
아래의 둘 코드를 비교하자면 위의 코드가 더욱 직관적으로 이해하기가 편하다.
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
Complex fulcrumPoint = new Complex(23.0);
참고로 생성자 사용을 제한시 해당 생성자를 private로 선언해야한다. (이펙티브 자바 링크 추가)
한 개념에 한 단어를 사용하라
추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다. 예를 들어, 똑같은 메서드를 클래스마다 fetch, retrive, get으로 각각 부르면 혼란스럽다.
메서드의 이름은 독자적이고 일관적이어야 한다. 그래야 주석을 뒤져보지 않고도 프로그래머가 올바른 메서드를 선택한다.
말장난을 하지마라
한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용한다면 그것은 말장난에 불과하다.
‘한 개념에 한 단어를 사용하라’느 규칙을 따랐더니, 예를 들어 여러 클래스에 add 라는 메서드가 생겼다. 모든 add 메서드의 매개변수와 반환값이 의미적으로 똑같다면 문제가 없다.
하지만 같은 맥락이 아닌데도 ‘일관성’을 고려해 add라는 단어를 선택하는 때가 있다.
예를들어, 기존의 add 메서드는 모두가 기존값 두 개를 더하거나, 새로운 값을 만든다고 가정하자. 새로 작성하는 메서드는 집합에 값 하나를 추가한다. 이 로직은 기존의 add에서 사용하는 로직과 맥락이 다르다. 그러므로 insert나 append라는 이름이 적당하다.
해법 영역에서 가져온 이름을 사용하라.
코드를 읽을 사람또한 프로그래머이다. 그러므로 전산용어, 알고리즘 이름, 패턴 이름, 수학 용어등을 사용해도 괜찮다. 되려 모든 이름을 문제 영역(domain) 에서 가져오는 정책은 현명하지 못한다.
VISITOR 패턴에 친숙한 프로그래머는 AccountVisitor 라는 이름을 금방 이해한다. JobQueue와 같이 프로그래머에게 익숙한 기술 개념은 많다.
문제 영역에서 가져온 이름을 사용하라.
적절한 ‘프로그래머 용어’가 없다면 문제 영역에서 이름을 가져온다. 그러면 코드를 보수하는 프로그래머가 분야 전문가에게 의미를 물어 파악 할 수 있다.
우수한 프로그래머와 설계자라면 해법 영역과 문제영역을 구분할 줄 알아야 한다. 문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 한다.
의미 있는 맥락을 추가하라
스스로 의미가 분명한 이름이 없지 않다. 하지만 대다수 이름은 그렇지 못하다. 그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.
예를 들어, firstName, lastName, street, houseNumber, city, state, zipcode 라는 변수가 있다. 변수를 훑어 보면 주소라는 사실을 금방 알아 챌 수 있다. 하지만 사용 되는 변수가 state 하나라면? state가 주소의 일부라는것을 알아 챌 수 있을까?
addr라는 접두어를 추가해 addrFirstName, addrLastName, addrState라 쓰면 맥락이 좀 더 분명해진다. 물론 Address라는 클래스를 생성해 안에 넣어주는것이 더좋다. 그러면 변수가 좀 더 큰 개념에 속한다는 사실이 컴파일러에게도 전해진다.
불필요한 맥락을 없애라
고급 휘발유 충전소 (Gas Station Deluxe)라는 애플리 케이션을 짜다고 가정하자. 모든 클래스 이름을 GSD로 시작하겠다는 생각은 전혀 바람직 하지 못하다.
IDE에서 G를 입력하고 자동완성 키를 누르면 IDE는 GSD로 시작하는 모든 클래스를 열거하게 될것이다.
일반적으로는 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해서이다. 이름이 불필요한 맥락을 추가하지 않도록 주의한다.
accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이지만, 클래스 이름으로는 적합하지 못하다. Address 클래슨 이름으로 적합하다. 만약 포트주소, MAC 주소, 웹 주소를 구분해야 한다면 PostalAddress, MAC, URI라는 이름도 괜찮다. 그러면 의미가 좀 더 분명해진다.