의도가 분명한 이름은 정말로 중요합니다. 변수(혹은 함수나 클래스는) 존재 이유, 수행 기능, 그리고 사용 방법을 분명히 드러내야 합니다.
// 의도가 드러나지 않는 변수 이름
let d; // 경과 시간(단위: 날짜)
// 의미를 표현하는 변수 이름
let elapsedTimeInDays;
let daysSinceCreation;
let daysSinceModification;
let fileAgeInDays;
문제는 코드의 단순성이 아니라 코드의 함축성입니다. 지뢰찾기 게임을 만든다고 가정합니다. 게임판에서 각 칸은 단순 배열로 표현합니다. 배열에서 0번째 값은 칸 상태를 뜻합니다. 값 4는 깃발이 꽂힌 상태를 가리킵니다.
// 변수와 상수의 정보가 드러나지 않는 코드
function getThem(): number[][] {
const list1: number[][] = [];
theList.forEach((x: number[]) => {
if (x[0] === 4) {
list1.push(x);
}
});
return list1;
}
// 각 개념에 이름을 붙여 정보를 제공한 코드
function getFlaggedCells(): number[][] {
const flaggedCells: number[][] = [];
gameBoard.forEach((cell: number[]) => {
if (cell[STATUS_VALUE] === FLAGGED) {
flaggedCells.push(cell);
}
});
return flaggedCells;
// Class를 사용하여 좀 더 개선한 코드
function getFlaggedCells(): Cell[] {
const flaggedCells: Cell[] = [];
gameBoard.forEach((cell: Cell) => {
if (cell.isFlaged()) {
flaggedCells.push(cell);
}
});
return flaggedCells;
}
프로그래머는 코드에 그릇된 단서를 남겨서는 안 됩니다. 그릇된 단서는 코드 의미를 흐립니다.
l
과 숫자 1
, 대문자 O
와 숫자 0
)컴파일러나 인터프리터만 통과하려는 생각으로 코드를 구현하는 프로그래머는 스스로 문제를 일으킵니다. 컴파일러를 통과할지라도 연속된 숫자를 덧붙이거나 불용어(Noise Word)를 추가하는 방식은 적절하지 못합니다. 이름이 달라야 한다면 의도도 달라져야 합니다.
// 아무런 정보를 제공하지 못하는 연속적인 숫자를 붙인 이름
// 인수 이름으로 source, destination을 사용하여 개선
function copyChars(a1: string[], a2: string[]): void {
for (let i = 0; i < a1.length; i += 1) {
a2[i] = a1[i];
}
}
불용어를 추가한 이름 역시 아무런 정보도 제공하지 못합니다. a
나 the
와 같은 접두어를 사용하지 말란 소리가 아닙니다. 의미가 분명히 다르다면 사용해도 무방합니다.
theZork
와 zork
, moneyAmount
와 money
, customerInfo
와 customer
는 의미가 구분되지 않습니다.NameString
은 Name
돠 나은 점이 없습니다. Name
이 숫자면 그릇된 정보를 피하라 규칙에 어긋납니다.발음하기 쉬운 이름을 선택해야 합니다. 발음하기 어려운 이름은 토론하기도 어렵습니다. 프로그램밍은 사회 활동이기 떄문입니다.
class DraRcrd102 {
private genymdhms: Date;
private modymdhms: Date;
private readonly pszqint: string = "102";
/* ... */
}
class Customer {
private generationTimestamp: Date;
private modificationTimestapm: Date;
private readonly recordId: string = "102";
/* ... */
}
문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 눈위 띄지 않는다는 문제점이 있습니다. WORK_DAYS_PER_WEEK
는 grep으로 찾기 쉽지만, 숫자 5
은 은근히 까다롭습니다. 이름 길이는 범위 크기에 비례해야 합니다. 변수나 상수를 코드 여러 곳에서 사용한다면 검색하기 쉬운 이름이 바람직합니다. 다음 두 예제를 비교해 봅니다.
for (let j = 0; j < 34; j += 1) {
s += (t[j] * 4) / 5;
}
const readDaysPerIdealDay: number = 4;
const WORK_DAYS_PER_WEEK: number = 5;
let sum: number = 0;
for (let j = 0; j < NUMBER_OF_TRACKS; j += 1) {
const realTaskDays: number = taskEstimate[j] * readDaysPerIdealDay;
const realTaskWeeks: number = realTaskDays / WORK_DAYS_PER_WEEK;
sum += realTaskDays;
}
독자가 코드를 읽으면서 변수 이름을 자신이 아는 이름으로 변환해야 한다면 그 변수 이름은 바람직하지 못합니다. 이는 일반적으로 문제 영역이나 해법 영역에서 사용하지 않는 이름을 선택했기 때문에 생기는 문제입니다.
클래스 이름과 객체 이름은 명사나 명사구가 적합합니다.
Customer
, WikiPage
, Account
, AddressPareser
Manager
, Processor
, Data
, Info
메서드 이름은 동사나 동사구가 적합합니다. postPayment
, deletePage
, save
등이 좋은 예입니다. 접근자(Accessor), 변경자(Mutator), 조건자(Predicate) 값 앞에 get
, set
, is
를 붙입니다.
생성자(Consturctor)를 중복정의(Overload)할 때는 정적 팩토리 메서드를 사용합니다. 메서드 인수를 설명하는 이름을 사용합니다.
const fulcrumPoint: Complex = Complex.FromRealNumber(23.0);
이름이 너무 기발하면 저자와 유머 감각이 비슷한 사람만, 길고 농담을 기억하는 동안만, 이름을 기억합니다. 특정 문화에서만 사용하는 농담은 피하는 편이 좋습니다. 의도를 분명하고 솔직하게 표현합니다.
추상적인 개념 하나에 단어 하나를 선택해 이를 고수합니다. 예를 들어, 똑같은 메서드를 클래스마다 fetch
, retrieve
, get
으로 제각각 부르면 혼란스럽습니다. 이름이 다르면 독자는 당연히 클래스도 다르고 타입도 다르리라 생각합니다.
한 단어를 두 가지 목적으로 사용하면 안됩니다. 한 개념에 한 단어를 사용하라는 규칙을 따르되, 같은 맥락이 아닌 코드에는 다른 이름을 사용합니다. 새 메서드가 기존 add
메서드와 맥락이 다르면, insert
나 append
라는 이름이 적당합니다.
코드를 읽는 사람도 프로그래머라는 사실을 명심합니다. 그러므로 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮습니다. 모든 이름을 문제 영역(Domain)에서 가져오는 정책은 현명하지 못합니다.
적절한 프로그래머 용어가 없거나 문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 합니다. 그러면 코드를 보수하는 프로그래머가 분야 전문가에게 의미를 물어 파악할 수 있습니다.
스스로 의미가 분명한 이름이 없지 않습니다. 하지만 대다수 이름은 그렇지 못합니다. 그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여합니다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙입니다.
// 함수 전체를 훑어보지 않고서는 변수의 의미를 유추하기 어려운 코드
function printGuessStatistics(candidate: string, count: number) {
let number: string;
let verb: string;
let pluralModifier: string;
if (count === 0) {
number = "no";
verb = "are";
pluralModifier = "s";
} else if (count === 1) {
number = "1";
verb = "is";
pluralModifier = "";
} else {
number = count.toString();
verb = "are";
pluralModifier = "s";
}
const quessMessage: string = `There ${verb} ${number} ${candidate}${pluralModifier}`;
console.log(quessMessage);
}
함수를 작은 조각으로 쪼개고자 GuessStatisticsMessage
라는 클래스를 만든 후 세 변수를 클래스에 넣었습니다. 그러면 세 변수는 맥락이 분명해집니다. 즉, 세 변수는 확실하게 GuessStatisticsMessage
에 속합니다. 이렇게 맥락을 개선하면 함수를 쪼개기가 쉬우지므로 알고리즘도 좀 더 명확해 집니다.
class GuessStatisticsMessage {
private number: string;
private verb: string;
private pluralModifier: string;
public make(candidate: string, count: number): string {
this.createPluralDependentMessageParts(count);
return `There ${this.verb} ${this.number} ${candidate}${this.pluralModifier}`;
}
private createPluralDependentMessageParts(count: number): void {
if (count === 0) {
this.thereAreNoLetters();
} else if (count === 1) {
this.thereIsOneLetter();
} else {
this.thereAreManyLetters(count);
}
}
private thereAreManyLetters(count: number): void {
this.number = count.toString();
this.verb = "are";
this.pluralModifier = "s";
}
private thereIsOneLetter(): void {
this.number = "1";
this.verb = "is";
this.pluralModifier = "";
}
private thereAreNoLetters(): void {
this.number = "no";
this.verb = "are";
this.pluralModifier = "s";
}
}
접두어를 사용해 변수가 좀 더 큰 구조에 속한다는 사실이 적어도 독자에게 전달할 수도 있습니다. firstName
, lastName
, state
에 addr
라는 접두어를 추가해 addrFirstName
, addrLastName
, addrState
라 쓰면, 변수가 주소의 일부로 사용된다는 맥락이 좀 더 분명해집니다.
고급 휘발유 충전소(Gas Station Deluxe)라는 애플리케이션을 짠다고 가정합니다. 모든 클래스 이름을 GSD로 시작하겠다는 생각은 전혀 바람직하지 못합니다. 이는 IDE 성능을 방해하기도 합니다. 일반적으로는 짧은 이름이 긴 이름보다 좋습니다. 단, 의미가 분명한 경우에 한해서입니다. 이름에 불필요한 맥락을 추가하지 않도록 주의합니다.
우리들 대다수는 자신이 짠 클래스 이름과 메서드 이름을 모두 암기하지 못합니다. 암기는 요즘 나오는 도구에게 맡기고, 우리는 문장이나 문단처럼 읽히는 코드 아니면 적어도 표나 자료 구조처럼 읽히는 코드를 짜는 데만 집중해야 마땅합니다. 여느 코드 개선 노력과 마찬가지로 이름 역시 나름대로 바꿨다가는 누군가 질책할지도 모릅니다. 그렇다고 코드를 개선하려는 노력을 중단해서는 안 됩니다.