'이름 짓기'는 개발에서 가장 어려운 주제 중 하나일 것이다. 변수명, 함수명, 클래스명, 파일명, 디렉터리명 등... 이름을 잘 짓는 규칙을 알고 있으면 개발 과정이 여러 모로 수월해질 것이다. 클린 코드에서 읽은 몇 가지 이름 짓기 규칙을 소개한다.
의도가 분명한 이름은 정말로 중요하다. 이름에 의도가 분명히 드러난다는 것은, 별도의 주석 없이 다음 내용을 파악할 수 있어야 함을 의미한다.
이를 테면 다음과 같다.
# BAD: 아무 의미도 드러나지 않는 이름(d)
int d; // 경과 시간(단위: 날짜)
# GOOD: 측정하려는 값과 단위를 잘 표현하는 이름
int fileAgeInDays;
int daysSinceModification;
int daysSinceCreationl
int elapsedTimeInDays;
의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉬워진다. 가령 지뢰찾기 게임 개발에서 다음과 같은 코드가 있다고 해 보자.
# BAD Example
list1 = new ArrayList<int[]>();
for (int[] x: theList) {
if (x[0] == 4) {
list1.add(x)
}
}
위 코드에는 어려운 내용이 없지만, 이 코드를 처음 보는 사람은 코드가 하는 일을 짐작하기 어렵다. list1
, theList
, 0번째 원소
, 값 4의 의미
등의 맥락이 코드 자체에 명시적으로 드러나지 않기 때문이다.
위 코드를 아래와 같이 개선한다면, 지뢰찾기 게임에서 어떤 역할을 하는 코드인지가 보다 명확해진다. 단순히 이름만 고쳤는데도 말이다.
# GOOD Example
flaggedCells = new ArrayList<int[]>();
for (int[] cell: gameBoard) {
if (cell[STATUS_VALUE] == FLAGGED) {
flaggedCells.add(cell)
}
}
# BETTER Example
flaggedCells = new ArrayList<Cell>();
for (Cell cell: gameBoard) {
if (cell.isFlagged()) {
flaggedCells.add(cell)
}
}
그릇된 단서는 코드 의미를 흐린다.
1. 나름대로 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해서는 안된다.
예를 들면, ctx
는 context
의 약어로 자주 쓰이지만, 해양생물학자가 이를 본다면 코노톡신(Conotoxin)
이라는 단어를 가장 먼저 떠올릴 것이다. 또한 hp
는 빗변(hypotenuse)
를 구현할 때 좋은 약어로 보일지 몰라도, 게임에서 체력을 나타내는 Health Point
의 의미로 혼동될 수도 있다.
2. 여러 계정(account)를 그룹으로 묶을 때, 실제 list 자료형이 아니라면 가급적 -List
형태로 명명하지 않는다.
list는 프로그래머에게 복수의 목록 그 이상의 의미를 갖는다. 그러므로 accountGroup
, bunchOfAccounts
, Accounts
등의 복수 표현이 의미가 있을 것이다.
3. 서로 흡사한 이름을 사용하지 않도록 주의한다.
ControllerForEfficientHandlingOfStrings
와 ControllerForEfficientStorageOfStrings
는 서로 아주 비슷하다. 그만큼 혼동도 많이 온다.
4. 유사한 개념은 유사한 표기법을 사용한다.
일관성이 보장된 표기법 역시 정보이다. 따라서 일관성이 떨어지는 표기법은 그릇된 정보다. 타인이 만든 객체나 클래스의 역할을 파악할 때, 상세한 주석까지 일일이 살펴보고 있자면 시간이 많이 든다. 표기법이 일관적이면서 개념 차이를 명확히 드러낼 수 있다면 가장 좋다.
모든 이름은 그 이름마다 각각의 고유한 의도가 드러나야 한다. 따라서 a1, a2, ..., aN
이나 test, testtest, testtesttest
등과 같이 의도도 드러나지 않고 의미도 없는 구분은 코드에서 지양해야 한다.
불용어를 추가한 이름 역시 정보 제공 측면에서는 큰 도움이 안 된다. ProductData
와 ProductInfo
는 서로 무엇이 다르고, 각각은 Product
와 어떤 점이 다른가? getActiveAccount(), getActiveAccounts(), getActiveAccountInfo()
는 어떻게 다른가? 개념 구분이 분명하지 않다. 이처럼 의미가 불분명한 단어는 a, an, the와 같은 불용어와 같다.
중복도 일종의 불용어다. 사람의 혈액을 나타낼 때 RedBlood와 Blood는 거의 차이가 없다. 사람의 혈액이 빨간색이 아닐 수는 없다(건강 상태에 따라 검붉은 색이 될 수는 있지만, 그렇다고 검은색으로 인식하지는 않는다). NameString과 Name은 별반 다를 것이 없다. 이름이 number형이 될 가능성은 없잖은가?
요점은, 읽는 사람이 차이를 알도록 이름을 지어야 한다는 것이다.
말 그대로, 발음하기 쉬운 이름을 선택하는 것이 그렇지 않는 것보다 생산적이다. 현재 시각(current timestamp)을 의미하는 데이터를 나타내기 위해 crts
라는 변수명을 쓴다고 가정해 보자. 그것을 부르기 위해 크르츠
라는 우스꽝스러운 단어를 발음해야 할 것이다. 차라리 currentTimestamp
라는 이름을 쓰고 커런트 타임스탬프
라고 발음하는 것이 훨씬 의미도 분명하다.
문자 하나를 사용하는 이름과 상수는 코드에서 쉽게 눈에 띄지 않는다. 변수나 상수를 코드 여러 곳에서 사용한다면, 검색하기 쉬운 이름이 바람직하다.
예를 들어, s
와 sum
중 어떤 것이 의미가 더 분명한가? 또, 근태관리 프로그램의 코드에서 조건문에 단지 5
만 있는 것과 WORK_DAYS_PER_WEEK = 5
으로 이름이 지어져있는 것 중 어느 쪽이 더 이해하기 쉽겠는가?
이름에 부가 정보를 덧붙여 표기하는 것을 피하라는 의미이다.
phoneString
, isFavorBool
과 같이 변수에 자료형을 표시하는 표기법이다. 과거 컴파일러가 타입을 점검하지 않을 때 프로그래머에게 타입을 기억할 단서를 제공하기 위한 표기법이다.
멤버 변수를 표한하기 위해 m_
접두어를 붙여 이름 짓는 것을 의미한다.
인터페이스 클래스 이름과 구현 클래스 이름 중 하나를 인코딩해야 한다면, 구현 클래스 이름에 정보를 인코딩하는 것이 낫다.
독자가 읽을 코드는 명료해야 한다. 즉 독자가 어떤 이름이 무엇을 가리키는지 인식하기 위해 머릿속에서 한번 더 생각해서 자신이 아는 이름으로 변환해야 할 정도의 이름은, 바람직하지 못하다. 이는 문제 영역이나 해법 영역에서 사용하지 않는 이름을 선택하면 발생할 수 있는 문제다.
명료한 이름은 남들도 이해하기 쉽다.
클래스 이름, 객체 이름은 명사나 명사구가 적합하다(Ex. Customer
, WikiPage
, AddressParser
). 동사는 사용하지 않으며, 다소 추상적인 의미를 갖는 Manager
, Processor
, Data
, Info
등의 이름은 피한다.
동사나 동사구가 적합하다. postPayment
, deletePage
, save
등이 좋은 예다.
접근자(Accessor), 변경자(Mutator), 조건자(Predicate)는 값 앞에 get
, set
, is
를 붙인다.
string name = user.getName();
customer.setName("mike")
if (payCheck.isPosted()) ...
재미있는 이름보다 명료한 이름이 좋다. 또한, 특정 문화에서만 사용하는 속어, 농담은 피하는 편이 좋다.
- HolyHandGrenade(X)
- DeleteItems(O)
- eatMyShort(X)
- Abort(O)
추상적인 개념 하나에 단어 하나를 선택해 일관성 있게 사용한다. 예를 들어, 똑같은 메서드를 클ㄹ래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다. 과거 코드를 살피느라 시간을 쏟게 된다. 따라서 이름은 독자적이고 일관적이어야 한다.
한 단어를 두 가지 목적으로 사용하지 않는다. 또, 다른 개념에 같은 단어를 사용하지 않는다. 위에서 말한 일관성은 같은 맥락일 때만 유효하다. 비슷한 행위라도 맥락이 다르다면 다른 단어를 사용해야 한다. 기존 값 두 개를 더하는 메서드와 집합에 새로운 값 하나를 추가하는 메서드는 둘 다 무언가를 '더하는' 행위이다. 하지만 이들 모두를 add
라고 불러도 될까? 일관성을 지키기 위해서는 그래야만 할 것 같지만, 두 메서드는 맥락이 다르다. 따라서 전자는 add
, 후자는 insert
나 append
가 적절하다.
코드는 최대한 이하기 쉽게 짜야 한다. 집중적으로 탐구해야만 이해할 수 있는 코드가 아닌, 대충 훑어봐도 이해할 코드 작성이 목표다.
코드를 읽는 사람은 고객이 아니라 프로그래머이다. 따라서 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다. 모든 이름을 도메인 영역에서 가져올 필요는 없다.
적절한 프로그래머 용어가 없다면 도메인 영역에서 이름을 가져오면 된다. 가장 좋은 것은 해법 영역과 도메인 영역을 구분할 줄 알고, 코드가 둘 중 어느쪽에 관련이 있는지 판단하여 해당 영역에서 이름을 가져올 수 있는 능력을 갖추는 것이다.
때때로 이름은 맥락이 분명해야 의미도 분명해진다. 즉, 스스로 의미가 분명하지 않은 이름들이 있다는 뜻이다. 예를 들어 state
라는 다의어는 단독으로 쓰이면 무슨 의미인지 특정할 수 없다. 하지만 접두어로 addr
이 붙어 addrState
라는 변수명은 어떨까? state가 주소의 일부라는 사실을 짐작할 수 있다.
채팅 애플리케이션을 만든다고 가정하자. 모든 클래스 이름에 접두어 Chat
을 붙이는 것은 어떨까? 맥락 추가가 너무 과하다.
다른 예시로, Address
는 클래스 이름으로 적합하다. Address
클래스로 accountAddress
, customerAddress
클래스 인스턴스를 만든다면 그 이름은 클래스 인스턴스로서 적합하다. 반대로 accountAddress
, customerAddress
는 클래스 이름으로는 적절하지 못하다.
이름에 너무 불필요한 맥락을 추가하지 않도록 주의해야 한다. 그래서 일반적으로 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해서다.