Book Notes

[개발서적] Clean Code 2. 의미 있는 이름

feel2 2025. 10. 20. 08:57

[의도를 분명히 밝혀라]

“의도가 분명하게 이름을 지으라”고 말하기는 쉽다. 여기서는 의도가 분명한 이름이 정말로 중요하다는 사실을 거듭 강조한다.

변수나 함수 그리고 클래스 이름은 다음과 같은 굵직한 질문에 모두 답해야 한다.

 

  • 변수(혹은 함수나 클래스)의 존재 이유는?
  • 변수(혹은 함수나 클래스)의 수행 기능은?
  • 변수(혹은 함수나 클래스)의 사용 방법은?
int d; // 경과 시간(단위: 날짜)

 

이름 d는 아무 의미도 드러나지 않는다. 따라서 다음과 같이 표현해야 한다.

 

int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

 

이처럼 표현하면 코드 이해와 변경이 쉬워진다.

 

[의미 있게 구분하라]

불용어를 추가한 이름은 아무런 정보도 제공하지 못한다.

예를 들어 Product 클래스가 있다고 가정하자. 다른 클래스를 ProductInfo 혹은 ProductData라 부른다면 개념을 구분하지 않은 채 이름만 달리한 경우다. Info나 Data는 a, an, the와 마찬가지로 의미가 불분명한 불용어다.

 

또 다른 사례를 살펴보자.

  • getActiveAccount();
  • getActiveAccounts();
  • getActiveAccountInfo();

이 프로젝트에 참여한 프로그래머는 어느 함수를 호출할지 어떻게 알까?

 

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

사람들은 단어에 능숙하다. 그러므로 발음하기 쉬운 이름을 선택한다.

내가 아는 회사 하나는 genymdhms라는 단어를 사용했다. 어떻게 읽고 해석이 가능할까?

 

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

문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다는 문제점이 있다.

MAX_CLASSES_PER_STUDENT 는 grep으로 찾기가 쉽지만, 숫자 7은 은근히 까다롭다.

 

마찬가지로 e라는 문자도 변수 이름으로 적합하지 못하다. 검색이 어려운 탓이다.

이런 관점에서 긴 이름이 짧은 이름보다 좋다. 검색하기 쉬운 이름이 상수보다 좋다.

 

다음 두 예지를 비교해보자.

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;
}

 

위 코드에서 sum이 별로 유용하진 않으나, 취소한 검색이 가능하다. 이름을 의미있게 지으면 함수가 길어진다. 하지만 WORK_DAYS_PER_WEEK를 찾기가 얼마나 쉬운지 생각해보라.

 

[인코딩을 피하라]

굳이 부담을 더하지 않아도 이름에 인코딩할 정보는 아주 많다. 문제 해결에 집중하는 개발자에게 인코딩은 불필요한 정신적 부담이다.

개인적으로 인터페이스 이름은 접두어를 붙이지 않는 편이 좋다고 생각한다.

 

예를 들어 인터페이스가 ShapeFactory이면 구현 클래스 이름을 ShapeFactoryImpl나 심지어 CShapeFactory가 좋다.

 

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

독자가 코드를 읽으면서 변수 이름을 자신이 아는 이름을 변환해야 한다면 그 변수 이름은 바람직하지 못하다.

문자 하나만 사용하는 변수 이름은 문제가 있다. 루프에서 반복 횟수를 세는 변수 i, j, k는 괜찮다.

 

단, 루프 범위가 아주 작고 다른 이름과 충돌하지 않을 때만 괜찮다. 그 외에는 대부분 적절하지 못하다.

 

클래스 이름

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

 

메서드 이름

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

생성자를 중복정의할 때는 정적 팩토리 메서드를 사용한다.

Complex fulcrumPoint = Complex.fromRealNumber(23.0);

 

위 코드가 아래 코드보다 좋다.

Complex fulcrumPoint = new Complex(23.0);

 

생성자 사용을 제한하려면 해당 생성자를 private로 선언한다.

 

[한 개념을 한 단어에 사용하라]

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

 

마찬가지로 동일 코드 기반에 controller, manager, driver를 섞어 쓰면 혼란스럽다.

일관성 있는 어휘는 코드를 사용할 프로그래머가 반갑게 여길 선물이다.

 

[말장난을 하지 마라]

한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용한다면 그것은 말장난에 불과하다.

 

예를 들어, 지금까지 구현한 add 메서드는 모두가 기존 값 두 개를 더하거나 이어서 새로운 값을 만든다고 가정하자.

새로 작성하는 메서드는 집합에 값 하나를 추가한다. 이 메서드를 add라 불러도 괜찮을까?

 

프로그래머는 코드를 최대한 이해하기 쉽게 짜야 한다. 의미를 해독할 책임이 독자에게 있는 논문 모델이 아니라 의도를 밝힐 책임이 저자에게 있는 잡지 모델이 바람직하다.

 

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

코드를 읽는 사람도 프로그래머라는 사실을 명심한다. 그러므로 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다.

VISITOR 패턴에 친숙한 프로그래머는 AccountVisitor라는 이름을 금방 이해한다.

JobQueue를 모르는 프로그래머가 있을까?

 

프로그래머에게 익숙한 기술 개념은 아주 많다. 기술 개념에는 기술 이름이 가장 적합한 선택이다.

 

[의미 있는 맥락을 추가하라]

스스로 의미가 분명한 이름은 있다. 하지만 대다수 이름은 그렇지 못하다. 그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다.

모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.

 

다음 메서드를 살펴보자

private void printGuessStatistics(char candidate, int count) {
    String number;
    String verb;
    String pluralModifier;

    if (count == 0) {
        number = "no";
        verb = "are";
        pluralModifier = "s";
    } else if (count == 1) {
        number = "1";
        verb = "is";
        pluralModifier = "";
    } else {
        number = Integer.toString(count);
        verb = "are";
        pluralModifier = "s";
    }

    String guessMessage = String.format(
        "There %s %s %s%s", verb, number, candidate, pluralModifier
    );
    print(guessMessage)
}

 

일단 함수가 좀 길다. 그리고 세 변수를 함수 전반에서 사용한다. 위를 수정하면 다음과 같다.

 

public class GuessStatisticsMessage {
    private String number;
    private String verb;
    private String pluralModifier;

    public String make(char candidate, int count) {
        createPluralDepedentMessageParts(count);

        return String.format(
            "There %s %s %s%s",
            verb, number, candidate, pluralModifier
        );
    }

    private void createPluralDepedentMessageParts(int count) {
        if (count == 0) {
            thereAreNoLetters();
        } else if (count == 1) {
            thereIsOneLetter();
        } else {
            thereAreManyLetters(count);
        }
    }

    private void thereAreManyLetters(int count) {
        number = Integer.toString(count);
        verb = "are";
        pluralModifier = "s";
    }

    private void thereIsOneLetter() {
        number = "1";
        verb = "is";
        pluralModifier = "";
    }

    private void thereAreNoLetters() {
        number = "no";
        verb = "are";
        pluralModifier = "s";
    }
}

 

GuessStatisticsMessage라는 클래스를 만든 후, 세 변수를 클레스에 넣었다. 그러면 세 변수는 맥락이 분명해진다.

즉, 세 변수는 확실하게 GuessStatisticsMessage 속한다.

 

이렇게 맥락을 개선하면 함수를 쪼개기가 쉬워지므로 알고리즘도 좀 더 명확해진다.

 

[불필요한 맥락을 없애라]

일반적으로 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해서다. 이름에 불필요한 맥락을 추가하지 않도록 주의한다.

 

accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이나 클래스 이름으로는 적합하지 못하다.