220201 의미 있는 이름

2. 의미 있는 이름

클린 코드를 읽고 공부한 내용을 타입스크립트로 정리해본 내용입니다. 개인적으로 공부한 내용이라 틀린점도 있을 수 있습니다.

의도를 분명히 밝혀라

변수나 함수 그리고 클래스의 이름은 다음과 같은 굵직한 질문에 모두 답해야 한다. 존재 이유는? 수행 기능은? 사용 방법은? 따로 주석이 필요하다면 의도를 분명히 드러내지 못햇다는 말이다.

const d: number

const daysSinceCreation: number

코드의 함축되어 있는 정보를 독자로 하여금 이해할 수 있도록 명시적으로 이름을 지어야 한다.

좋지 않은 예시 : ❌

1
2
3
4
5
6
7
8
9
function getThem() {
const list1: number[] = []
for (let x of theList) {
if (x[0] === 4) {
list1.push(x)
}
}
return list1
}

드러나지 않은 함축적인 정보는 다음과 같다.

  1. theList에 무엇이 들어있는가?
  2. theList 에서 0 번째 값이 어째서 중요한가?
  3. 값 4는 무슨 의미인가?
  4. 함수가 반환하는 리스트 list1을 어떻게 사용하는가?

만약 해당 함수가 지뢰찾기에서 깃발이 꽂힌 상태에 cell를 반환하는 함수라고 생각한다면 함축적인 의미를 이름을 통해서 명시적으로 드러낼 수 있다.

좋은 예시 : ✅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export interface Cell {
isFlagged: () => boolean;
data: number[];
}
const FLAGGED_STATE = 4;
class CellImpl implements Cell {
data: number[] = [0];
constructor(flaggedState: number) {
this.data[0] = flaggedState;
}
isFlagged = () => {
return this.data[0] === FLAGGED_STATE;
};
}

const gameBoard: Cell[] = [new CellImpl(FLAGGED_STATE)];
function getFlaggedCells() {
const flaggedCells: Cell[] = [];
for (let cell of gameBoard) {
if (cell.isFlagged()) {
flaggedCells.push(cell);
}
}
return flaggedCells;
}
  1. theLIst 가 gameBoard 로 무엇이 들어가 있느지 알수 있게 되었다.
  2. theList 에 0번째 값은 칸 상태, 값 4는 깃발이 꽂힌 상태라는 의미인데 cell.isFlagged() 로 명확하게 표현할 수 있게 되었다.
  3. 함수가 반환하는 리스트 list1 은 깃발이 꽂힌 상태에 cell 이라는 flaggedCells 이름으로 명확해진다.

그릇된 정보를 피하라

이름으로 잘못된 정보를 주어서는 안된다.

  1. 널리쓰이는 의미가 있는 단어를 다른 의미로 사용하지 말것
  2. 특수한 의미에 단어를 의미에 맞게 사용할것 (List 를 사용할 경우 List인 경우에만 사용할것 아니라면 accountGroup 이나 accounts 가 낫다)
  3. 서로 흡사한 이름을 사용하지 말것
  4. 유사한 개념은 유사한 표기법을 사용할것 (일관성 유지)

의미 있게 구분하라

이름이 달라야 한다면 의미도 달라져야 한다. (a1, a2) 와 같은 구분에 변수는 아무런 의미도 전달하지 못한다.

불용어를 남용 하지 말아야 한다. ProudctInfo 나 ProductData 에서 Info 나 Data 는 명확한 구분이 되지 못하고 영어에서 a, an, the와 같이 사용될 뿐이다. 접두어를 사용하지 말란 의미가 아니라 접두어를 사용하여 구분하는 의미가 있어야 한다는 것이다.

NameString 과 Name 은 차이가 없다. Customer 와 CustomerObject 도 마찬가지이다.

읽는 사람이 차이를 알도록 이름을 지어야 한다.

발음하기 쉬운 이름을 사용하라

발음하기 쉬운 단어를 사용해야 지적인 대화가 가능해집니다.

좋지 않은 예시 : ❌

1
2
3
4
5
6
class DtaRcrd102 {
private genymdhms: Date;
private modymdhms: Date;
private readonly pszqint = '102';
// ...
}

발음하기 쉬운 단어를 사용한 좋은 예시 : ✅

1
2
3
4
5
6
class Customer {
private generationTimestamp: Date;
private modificationTimestamp: Date;
private readonly recordId = '102';
// ...
}

검색하기 쉬운 이름을 사용하라

긴 이름이 짧은 이름보다 좋다. 검색하기 쉬운 이름이 상수보다 좋다. 이름 길이는 범위 크기에 비례해야 한다.

좋지 않은 예시 : ❌

1
2
3
4
5
let s = 0;
const t: number[] = [];
for (let j = 0; j < 34; j++) {
s += (t[j] * 4) / 5;
}

좋은 예시 : ✅

1
2
3
4
5
6
7
8
9
10
const NUMBER_OF_TASKS = 34;
const realDaysPerIdealDay = 4;
const WORK_DAYS_PER_WEEK = 5;
let sum = 0;
const taskEstimate: number[] = [];
for (let j = 0; j < NUMBER_OF_TASKS; j++) {
const realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
const realTaskWeeks = realTaskDays / WORK_DAYS_PER_WEEK;
sum += realTaskWeeks;
}

WORK_DAYS_PER_WEEK 을 단순히 5라고 하였을때 문제가 생겨 변경해야 한다면 5로 검색하기는 쉽지 않을 것이다. 그러나 WORK_DAYS_PER_WEEK 으로 명시했을 경우 쉽게 검색할 수 있다.

인코딩을 피하라

인코딩한 이름은 발음하기 어려우며 오타가 생기기도 쉽다.

헝가리식 표기법

과거에는 타입을 컴파일러가 검사하지 못했기 때문에 접두어에 짧은 문자를 더해서 타입을 표현하곤 했다. 요즘에 언어들은 컴파일러가 타입을 강제하고 기억하고 있기 때문에 이렇게 하는것이 오히려 방해가 되기도 한다. 또한 타입에 변환을 어렵게 한다. phoneNumber 를 string으로 타입을 바꾸어도 여전히 이름은 phoneNumber 이기 때문에 타입을 오해하기도 쉽다.

멤버 변수 접두어

멤버 변수에 m_ 와 같은 접두어를 붙힐 필요도 없다.

인터페이스 클래스와 구현 클래스

인터페이스와 구현 클래스를 구분하기 위해 인코딩이 필요하기도 하다. 과거에는 인터페이스에 IShapeFactory , 구현 클래스에 ShapeFactory 와 같은 방식으로 I 를 붙히기도 했는데 과도한 정보를 주는 느낌도 있어서 인터페이스를 ShapeFactory 와 같이 쓰고 구현체를 ShapeFactoryImpl 과 같이 사용하는것을 추천한다.

자신의 기억력을 자랑하지 마라.

변수 이름을 자신이 아는 이름으로 변환해야 한다면 그 변수 이름은 바람직하지 못하다.

문자 하나만 사용하는것도 루프와 같이 짧은 범위 내에서 사용하는것을 제외하면 바람직하지 못하다. 나중에 무슨 의미였는지 기억하기 어렵다.

기억하기 쉬운 명로함이 최고임을 기억해라

클래스 이름

클래스 이름이나 객체 이름은 명사나 명사구가 적절하다. Customer, AddressParser 은 좋은 예이다. Manager, Precessor, Data, Info 등과 같은 단어는 피하고 동사는 사용하지 않는다.

메서드 이름

메서드 이름은 동사나 동사구가 적합하다. postPayment, deletePage, save 등은 적합하다.

기발한 이름은 피하라

재미난 이름보다는 명로한 이름을 선택하라

한 개념에 한 단어를 사용하라

추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다. 예를 들어 똑같은 메서드를 클래스마다 fetch, retrieve, get 으로 제각각 부르면 혼란스럽다. 메서드 이름은 독자적이고 일관적이어야 한다.

이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르다고 생각한다. 예를들어 DeviceManager와 ProtocolController이 근본적으로 하는일이 똑같다면 하나로 통일시켜야 한다.

말장난을 하지 마라

한 단어를 두가지 목적으로 사용하지 말아야 한다.

맥락이 같다면 중복하여 사용해도 되지만 맥락이 다르다면 다른 단어를 사용해야 한다. 예를들어 클래스에 add 라는 메서드를 추가한다고 생각해 보자 어떤 매개변수 두개를 받아서 두개를 조합해 새로운 값을 반환하는 맥락이라면 여러 클래스에서 add 라는 이름에 메서드를 동일하게 사용해도 된다. 그런데 어떤 클래스에서는 어떤 값을 받아서 리스트에 더하는 메서드를 추가한다고 생각해보자. 이럴 경우 add 라는 단어를 사용하는것이 적합할까? 이럴 경우는 앞에서와 맥락이 다르므로 다른 메서드 이름을 사용해야 한다. 이 경우 insert 나 append 가 적합해 보인다.

해법 영역에서 가져온 이름을 사용하라

코드를 읽는 사람도 프로그래머 임을 기억해라. 프로그래머에게 익숙학 기술 개념은 아주 많으며 해당 개념 용어를 사용하는 것은 이해 관점에서도 아주 바람직하다. VISITOR 패턴에 친숙한 프로그래머는 AccountVisitor 라는 이름을 금방 이해할 것이다.

문제 영역에서 가져온 이름을 사용하라

적절한 프로그래머 용어가 없다면 문제 영역에서 이름을 가져온다.

의미 있는 맥락을 추가하라

클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다. 예를 들어서 firstName, lastName, street, state, zipcode .. 와 같은 변수를 사용한다면 주소라는 사실을 금방 알아챈다. 하지만 state 라는 변수 하나만 매개변수로 사용한다면 주소에 일부분이라고 생각할 수 있을까? 좀 더 큰 구조(주소) 에 속한다는 것을 명시하기 위해서 addr 이라는 접두어를 사용할 수 있다. addrFirstName, addrState…

또는, 맥락에 맞는 클래스를 생성하는것이 더 도움이 된다. 예를 들어 밑에 나오는 코드에서 num, verb, pluraModifier 는 통계 추측에 사용되는 변수이다. 단순히 사용되기만 한다면 어디에 사용되는 변수인지 코드를 다 읽지 않고서는 예측하기 힘들다. 이때 GuessStaticMessage 라는 클래스를 만들어 해당 변수를 멤버변수로 포함시킨다면 맥락을 더해서 어디에 사용되는 변수인지 명확히 표현할 수 있다.

좋지 않은 예시 : ❌

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function printGuessStatistics(candidate: string, count: number) {
let num: string;
let verb: string;
let pluralModifier: string;

if (count === 0) {
num = 'no';
verb = 'are';
pluralModifier = 's';
} else if (count === 1) {
num = '1';
verb = 'is';
pluralModifier = '';
} else {
num = String(count);
verb = 'are';
pluralModifier = 's';
}

const guessMessage = `There ${verb} ${num} ${candidate}${pluralModifier}`;
console.log(guessMessage);
}

좋은 예시 : ✅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class GuessStatisticsMessage {
private num: string = '';
private verb: string = '';
private pluralModifier: string = '';

make(candidate: string, count: number) {
this.createPluralDependentMessageParts(count);
return `There ${this.verb} ${this.num} ${candidate}${this.pluralModifier}`;
}

private createPluralDependentMessageParts(count: number) {
if (count === 0) {
this.thereAreNoLetters();
} else if (count === 1) {
this.thereIsOneLetter();
} else {
this.thereAreManyLetters(count);
}
}

private thereAreManyLetters(count: number) {
this.num = String(count);
this.verb = 'are';
this.pluralModifier = 's';
}

private thereIsOneLetter() {
this.num = '1';
this.verb = 'is';
this.pluralModifier = '';
}

private thereAreNoLetters() {
this.num = 'no';
this.verb = 'are';
this.pluralModifier = 's';
}
}

num, verb, pluralModifier 가 GuessStatisticMessage 클래스 안에 있으므로 맥락상 통계 메세지를 만드는데 사용되는 멤버 변수란 것을 알게 된다.

불필요한 맥락을 없애라

불필요한 맥락은 없애야 한다. 예를들어 고급 휘발유 충전소 라는 애플리케이션을 짠다고 했을때, 모든 클래스에 GSD 접두어를 붙히는 것은 바람직하지 못하다.

일반적으로 의미가 분명하다면 짧은 이름이 긴 이름보다 좋다. accountAddress 와 customerAddress 는 Address 클래스 인스턴스로는 적합하나 클래스 이름으로는 부적합하다.