의미있는 이름
0. 들어가면서
- 우리는 변수에도 이름을 붙이고, 함수에도 이름을 붙이고, 인수와 클래스와 패키지에도 이름을 붙인다. 이렇듯 이름을 많이 사용하므로 이름을 잘 지으면 여러모로 편하다.
1. 의도를 분명히 밝혀라
-
의도가 분명한 이름은 정말 중요하다. 좋은 이름을 지으려면 시간이 걸리지만 좋은 이름으로 절약하는 시간이 훨씬 더 많다.
-
변수나 함수 그리고 클래스 이름은 다음과 같은 굵직한 질문에 모두 답해야 한다.
- 변수(혹은 함수나 클래스)의 존재 이유는?
- 수행 기능은?
- 사용 방법은?
=> 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이다.
적절하지 않은 변수 이름
int d; // 경과 시간(단위: 날짜)
적절한 변수 이름
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
=> 이름 d는 아무 의미도 드러내지 않는다. 경과 시간이나 날짜라는 느낌이 안 든다. 측정하려는 값(ex. elapsedTime)과 단위(ex. InDays)를 표현하는 이름이 필요하다.
적절하지 않은 코드
public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<>();
for (int[] x: theList) {
if (x[0] == 4)
list1.add(x);
}
return list1;
}
이 코드의 문제는 다음과 같은 내용을 명시하지 않아 코드가 하는 일을 짐작하기 어렵다는 것이다.
- theList에 무엇이 들었는가?
- theList에서 0번째 값이 어째서 중요한가?
- 값 4는 무슨 의미인가?
- 함수가 반환하는 리스트 list1을 어떻게 사용하는가?
적절한 코드
지뢰찾기 게임을 만든다고 하자. theList를 게임판으로 하면 gameBoard가 된다. 각 칸은 단순 배열로 표현되고 배열에서 0번째 값을 칸의 상태를 뜻하게 하자. 값 4는 깃발이 꼿힌 상태라 하자. 그럼 위의 의미없는 코드가 아래와 같은 가독성 있는 코드로 탈바꿈하게 된다.
public List<int[]> getFlaggedCells() {
List<int[]> flaggedCells = new ArrayList<>();
for (int[] cell: gameBoard) {
if (cell[STATUS_VALUE] != FLAGGED) { continue; }
flaggedCells.add(cell);
}
return flaggedCells;
}
한 걸음 더 나아가 int 배열을 사용하는 대신, 칸을 간단한 클래스로 만들어도 되겠다. isFlagged라는 좀 더 명시적인 함수를 사용해 FLAGGED 라는 상수를 감춰도 좋겠다. 다음과 같이
int[]
->Cell
cell[STATUS_VALUE] == FLAGEED
->cell.isFlagged()
public List<Cell> getFlaggedCells() {
List<Cell> flaggedCells = new ArrayList<>();
for (Cell cell: gameBoard) {
if (!cell.isFlagged()) { continue; }
flaggedCells.add(cell);
}
return flaggedCells;
}
==> 졸라 좋네.
2. 그릇된 정보를 피하라
-
프로그래머는 코드에 그릇된 단서를 남겨서는 안 된다. 그릇된 단서는 코드 의미를 흐린다.
-
여러 계정을 그룹으로 묶을 때, 실제 List가 아니라면, accountList라 명명하지 않는다. 프로그래머에게 List라는 단어는 특수한 의미다. 계정을 담는 컨테이너가 실제 List가 아니라면 프로그래머에게 그릇된 정보를 제공하는 셈이다. 그러므로
accountGroup
,bunchOfAccounts
, 아니면 단순히Accounts
라 명명한다. -
서로 흡사한 이름을 사용하지 않도록 한다. 예를 들어 한 모듈에서
XYZControllerForEfficientHandlingOfStrings
라는 이름을 사용하고, 조금 떨어진 모듈에서XYZControllerForEfficientStorageOfStrings
라는 이름을 사용한다면? 흡사해서 헷갈리므로 사용하지 말자. -
십중팔구 개발자는 (객체에 달린 상세한 주석이나 클래스가 제공하는 메서드 목록을 살펴보지 않은 채) 이름만 보고 객체를 선택한다. 따라서 애초에 이름을 잘 짓자!!
이름으로 그릇된 정보를 제공하는 진짜 끔찍한 예가 소문자 L이나 대문자 O 변수다. 두 변수를 한꺼번에 사용하면 더욱 끔찍해진다. 다음 코드에서 보듯, 소문자 L은 숫자 1처럼 보이고 대문자 O는 숫자 0처럼 보인다.
int a = l;
if (O == l)
a = O1;
else
l = 01;
- 위와 같은 변수를 도처에서 사용하는 코드를 실제로 보았다. 어떤 경우에는 코드 작성자가 글꼴을 바꿔(? ㅋㅋㅋ) 차이를 드러내는 해결책을 제안했다. 문서나 구전으로 미래 개발자 모두에게 알려야 하는 해결책이다.(알려주면 고맙지, 안 알려주면 신입은 지옥이다. ㅋㅋㅋㅋ) 이름만 바꾸면 문제가 깨끗이 풀린다. 괜스레 일거리를 만들 필요가 없다.
=> 코드 역할과 관련된 영어 키워드를 최대한 많이 알아야겠다.
3. 의미 있게 구분하라
-
동일한 범위 안에서는 다른 두 개념에 같은 이름을 사용하지 못한다. (ex.
class
라는 예약어가 있으므로 변수 이름으로class
를 사용하지 못한다.) 그래서 프로그래머가 한쪽 이름을 마음대로 바꾸고픈 유혹에 빠진다. 어떤 프로그래머는 철자를 살짝 바꿨다가 나중에 철자 오류를 고치는 순간 컴파일이 불가능한 상황에 빠진다. (ex.klass
->class
로 바꾸는 순간 컴파일 에러가 발생한다.) -
컴파일러를 통과할지라도 연속된 숫자를 덧붙이거나
불용어(noise word)
를 추가하는 방식은 적절하지 못하다. 이름이 달라야 한다면 의미도 달라져야 한다. 연속적인 숫자를 덧붙인 이름(a1, a2, …, aN)은 의도적인 이름과 정반대다. 이런 이름은 그릇된 정보를 제공하는 이름도 아니며, 아무런 정보를 제공하지 못하는 이름일 뿐이다. (한마디로쓰레기 이름
이다.) 저자 의도가 전혀 드러나지 않는다.
불용어가 들어간 코드
public static void copyChars(char a1[], char a2[]) {
for (int i = 0; i < a1.length; i++) {
a2[i] = a1[i];
}
}
=> 함수 인수 이름으로 source
와 destination
을 사용한다면 코드 읽기가 훨씬 쉬워진다.
고친 코드
public static void copyChars(char[] source, char[] destination) {
for (int i = 0; i < source.length; i++) {
destination[i] = source[i];
}
}
=> 훨씬 자연스럽네.
-
불용어를 추가한 이름 역시 아무런 정보도 제공하지 못한다.
Product
라는 클래스가 있다고 가정하자. 다른 클래스를ProductInfo
혹은ProductData
라 부른다면 개념을 구분하지 않은 채 이름만 달리한 경우다. Info나 Data는 a, an, the와 마찬가지로 의미가 불분명한 불용어다. -
중복도 불용어다. 변수 이름에 variable이라는 단어는 단연코 금물이다. 테이블 이름에 table이라는 단어도 마찬가지다. NameString이 Name보다 뭐가 나은가? Name이 부동소수가 될 가능성이 있던가? 그렇다면 앞서 언급한 “그릇된 정보를 피하라” 규칙을 위반한다.(Name이 부동소수가 될수 있다는 것 자체가 그릇된 정보이므로, 이름인데!) 코드를 읽다가 Customer라는 클래스와 CustomerObject라는 클래스를 발견했다면 차이를 알겠는가? 고객 급여 이력을 찾으려면 어느 클래스를 뒤져야 빠를까? => ㅋㅋㅋㅋ
getActiveAccount();
getActiveAccounts();
getActiveAccountInfo();
=> 이 프로젝트에 참여한 프로그래머는 어느 함수를 호출할지 어떻게 알까?
명확한 관례가 없다면 변수 moneyAmount
는 money
와 구분이 안 된다. customerInfo
는 customer
와, accountData
는 account
와, theMessage
는 message
와 구분이 안 된다.
- 읽는 사람이 차이를 알도록 이름을 지어라
4. 발음하기 쉬운 이름을 사용하라
genymdhms
: generate date, year, month, day, hour, minute, second
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";
}
5. 검색하기 쉬운 이름을 사용하라
for(int j = 0; j < 34; j++) {
s += (t[j] * 4) / 5;
}
int realDaysPerIdealDay = 4;
final int WORK_DAYS_PER_WEEK = 5;
int sumOfRealTaskWeeks = 0;
for(int j = 0; j < NUMBER_OF_TASKS; j++) {
int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
sumOfRealTaskWeeks += realTaskWeeks;
}