독서일기

[클린 코드] 2장 - 의미 있는 이름

Woonys 2022. 11. 6. 01:23
반응형

2장 - 의미 있는 이름

들어가면서

이름 잘 지으면? 여러모로 편하다는 걸 명심할 것. 여기서는 이름을 잘 짓는 규칙 몇 가지에 대해서 알아보도록 하자.

의도를 분명히 밝혀라

  • 의도가 분명한 이름이 정말로 중요하다.
  • 더 나은 이름이 떠오르면 개선해야 한다.

모든 클래스의 이름은 아래의 질문에 답변해야 한다.

  • 변수/함수/클래스의 존재 이유는?
  • 이들의 수행 기능은?
  • 사용 방법은?

이때 주석이 필요하다면 -> 변수명을 제대로 짓지 못했다는 것(의미를 잘 반영하지 못했다는 것이니)!

예시를 보자.

int d; // 경과 시간(단위: 날짜)

d에는 어떤 의미도 없다. 측정하려는 값과 단위를 표현하는 이름이 필요함.

int elapsedTimeInDays; // 날짜에서 잰 시간
int daysSinceCreation; // 만들어진 이래로 며칠이 지났는가
int daysSinceModification; // 수정한 이래로 며칠이 지났는가
int fileAgeInDays; // 파일의 수명

위의 날짜명은 모두 저마다 의미를 갖고 있으며 그 의미가 분명하게 드러난다.

예시 하나를 보자.

public List<int[]> getThem() {
    List<int[]> list1 = new ArrayList<int[]>();
    for (int[] x : theList) {
        if (x[0] ==4) {
            list1.add(x);
        }
    }
}

대체 getThem()이라는 놈의 정체가 뭘까? 뭔가(them)를 가져온다는 것까지는 알겠는데, 뭔 놈을 가져온다는 것이며 어떻게 가져오는 것인지
맥락이 전혀 드러나지 않는다. 여기에는 이 코드를 읽는 독자가 이미 알고 있을 것으로 전제하고 쓴 항목이 있는데, 아래와 같다.

  1. theList에는 무엇이 들어있는지
  2. theList에서 0번째 값이 왜 중요한지 -> x[0] == 4 를 체크하는 이유
  3. 2에서 값 4는 무슨 의미인지
  4. 함수가 반환하는 리스트 list1을 어떻게 사용하는지

만약 위의 예시가 지뢰찾기 게임이었다고 해보자. 이 경우, 각 숫자 및 변수에 의미를 보다 명확하게 드러낼 수 있다. theList는 게임판이었을테니, gameBoard로 바꾼다.
배열list1에서 0번째 값은 칸의 상태(아직 미개척 상태인지, 깃발이 꽂혔는지 등)를 의미하며, 값 4는 깃발이 꽂힌 상태를 말한다. 이렇게 각 개념에 이름을 붙여 다시 코드를 짜보자.

public List<int[]> getFlaggedCells() {
    List<int[]> flaggedCells = new ArrayList<int[]>();
    for (int[] cell : gameBoard) {
        if (cell[STATUS_VALUE == FLAGGED) {
            flaggedCells.add(cell);
        }
        return flaggedCells;
    }
}

여기서 몇 가지 더 추가해보자. int[] 배열을 쓰는 대신, cell 자체를 간단한 클래스로 만들어도 되겠다. isFlagged라는 더 명시적인 함수를 사용해 FLAGGED라는 상수를 감춰보자.

public List<Cell> getFlaggedCells() {
    for (Cell cell : gameBoard) {
        if (cell.isFlagged())
            flaggedCells.add(cell);
    return flaggedCells;
    }
}

이제는 훨씬 더 이 메소드를 이해하기가 쉬워졌다. 이것이 좋은 이름이 주는 위력이다.

그릇된 정보를 피하라

  1. 평소에 쓰는 단어를 다른 의미로 사용하게 될 경우
    • 독자로 하여금 헷갈릴 여지가 높다. 예컨대 ls는 리눅스에서 현재 디렉토리에 있는 파일을 전부 보여달라는 의미를 가지고 있다. 이를 다른 의미를 갖는 변수로
      사용한다면, 프로그래머에게 혼동을 줄 수 있다.
  2. CS 용어(ex. 자료구조)가 변수와 아무 상관없는데 변수명에 쓰는 경우
    • 여러 계정을 그룹으로 묶을 때, 그 타입이 List가 아니라면 xxxList라 명명하지 않는 것이 좋다. 개발자에게 List라는 단어는 특별한 의미를 갖기 때문이다(자연스럽게 자료형으로서 List를 떠올리게 되기 때문). 타입으로 List를 쓰지 않지만 그룹으로 묶어야 하는 경우,
      이름을 xxxGroup, 혹은 그냥 변수명을 복수(s를 붙인다던지)로 쓰는 것만으로도 충분하다.
  3. 서로 흡사한 이름을 사용하는 경우
    • 한 모듈에서 XYZControllerForEfficientHandlingOfStrings 라고 쓰고 다른 모듈에서 XYZControllerForEfficientStorageOfStrings 라고 쓰면? 차이를 알아차리기 힘들다. 웬만하면 서로 흡사한 이름을 쓰지 않도록 주의하자.
  4. 알파벳 l, O를 각각 1(I), 숫자 0과 헷갈리게 쓰는 경우

의미 있게 구분하라

  1. 변수에 연속된 숫자를 덧붙여 구분하는 방식은 적절하지 못하다.
    • 이름이 달라야 하면 의미도 달라져야 한다. 반면, 연속적인 숫자를 덧붙인 이름(a1, a2, ..., aN)은 아무런 정보를 제공하지 못할 뿐더러 이렇게 이름을 지은 의도 역시 전혀 드러나지 않는다.
      public static void copyChars(char a1[], char a2[]) {
      for (int i=0; i < a1.length; i++) {
         a2[i] = a1[i];
      }
      }
      메소드 이름으로부터 char[]를 카피하겠구나를 유추할 수 있지만, 디테일하게 메소드 내부 변수명만으로 어디서 어디로 카피하는지 잘 드러나지 않는다. 만약 a1, a2를 from, to 혹은 source, destination으로 바꾸면 방향이 훨씬 잘 드러난다.
  2. 불용어를 추가하는 방식 역시 좋지 않다.
    • Product라는 클래스가 있다고 하자. 이때, 다른 클래스 이름을 ProductInfo 혹은 ProductData라고 부르는 건 개념을 명확히 구분하지 않고 이름만 달리하는 경우이다. Product와 ProductData 간에 무슨 차이가 있는지 명확하게 설명할 수 있나? 없다.
    • 변수 이름으로 variable을 쓰는 것도 마찬가지다. 이들은 사실상 비슷한 의미의 단어를 중복으로 쓰는 것과 같다고 봐야 한다.
    • 그러니 읽는 사람이 차이를 알도록 이름을 짓자.

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

우리의 두뇌는 말을 처리하기 좋은 방식으로 발달했다. 그러니 변수명으로 발음하기 쉬운 단어를 선택하는 게 이해도 측면에서도 좋다. 이렇게만 보면 당연히 발음하기 좋은 단어를 쓰는 게 정상 아닌가?라고 생각할 수 있다. 한 가지 예시를 보자. 실제로 어떤 회사에서 썼다는 변수명이다.

genymdhms(generate date, year, month, day, hour, minute, second)

저걸 누가 어떻게 발음할 수 있을까...? 사실상 발음은 커녕 저 함수가 무슨 말을 줄인 것인지 알지 못하면 절대 이해할 수 없을 것이다.

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

문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다. 숫자를 로직 상에서 그대로 사용하거나 혹은 변수명을 s 이런 식으로 짓는다면 이걸 어떻게 찾을 수 있을까? 아래 코드를 보자.

Before

for (int j=0; j < 34; j++) {
    s += (t[j]*4)/5;
}

After

private final int realDaysPerIdealDay = 4;
private final int WORK_DAYS_PER_WEEK = 5;
private final int NUMBER_OF_TASKS = 34;
private 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;
}

이렇게 할 경우, 로직 자체의 의미도 명확할 뿐더러 검색할 때 역시 좋다. WORK_DAYS_PER_WEEK를 찾는 게 쉬울까, 5를 찾는 게 쉬울까?

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

독자가 코드를 읽으면서 변수 이름을 자기가 아는 이름으로 변환하는 과정을 머릿속에서 거쳐야 한다면 그 변수 이름은 바람직하지 못하다고 봐야 한다. 예컨대 문자 하나만 사용하는 변수 이름은 루프에서 반복 횟수를 셀 때(i, j, k 등)를 제외하고는 바람직하지 않다.

클래스/객체 이름은 명사/명사구가 적합하다

Customer, WikiPage, Account, AddressParser 등이 좋은 이름이다. 반면 같은 명사더라도 Manager, Processor, Data, Info 와 같이 그 의미가 명확하지 않은 불용어는 피하자. 동사는 사용하지 않는다.

메소드 이름은 동사/동사구가 적합하다

postPayment, deletePage, save 등과 같은 이름이 좋은 예시다. 접근자(getter), 변경자(setter), 조건자(isXXX)는 javabean 표준에 따라 값 앞에 get, set, is를 붙인다.

기발한 이름은 피하라

이름이 너무 기발하면 저자와 유머 감각이 비슷한 사람만, 그리고 농담을 기억하는 동안만 이름을 기억한다. kill() 대신에 whack()을 쓰는 게 그런 것이다. 의도는 분명하고 솔직하게 표현하며 특정 문화에서만 사용하는 농담은 피하자.

한 개념에 한 단어를 써라

추상적인 개념 하나에는 단어 하나를 선택한 뒤 이를 끝까지 고수한다. 뭔가를 가져오는 로직을 담은 메소드인데 어떤 클래스에서는 getXXX()로, 다른 곳에서는 fetchXXX()로 쓰는 건 좋지 않다.
마찬가지로, 동일 코드 기반에 controller, manager, driver를 섞어 쓰면 혼란스럽다. DeviceManager와 ProtocolController는 근본적으로 무엇이 다른가? 둘다 Controller 아닌가? 혹은 왜 둘다 Manager 아닌가?
어휘는 일관성 있게 써야 한다.

말장난 하지 마라

한 단어를 두 가지 목적으로 사용하지 말 것. 다른 개념에 같은 단어를 사용한다면 그것은 말장난에 불과하다. add라는 메소드를 생각해보자. 한 개념에 한 단어를 사용하라는 규칙을 따랐더니 여러 클래스에 add라는 메소드가 생겼다.
이때, 모든 add 메소드의 매개변수와 반환값이 의미적으로 똑같으면 문제없다. 하지만 같은 맥락이 아님에도 일관성을 고려해 add라는 단어를 선택하는 경우가 있다. 이제까지 add 메소드는 기존 값 두개를 더하거나 이어서 새로운 값을 더하는 메소드였다.
그런데 여기서 집합에 값 하나를 추가하는 로직 역시 add라는 메소드로 이름을 지었다고 해보자. 이게 타당할까? 놉. 이 새 메소드는 기존의 add 메소드와 맥락이 다르다. 따라서 insert, append라는 이름을 넣는 게 좋다. 과도한 일관성을 지키는 건 일관성이 아닌 말장난이다.

프로그래머에게 친숙한 이름은 써도 된다

맥락에 맞는 기술을 적용했다면 그 기술을 메소드 명에 써도 좋다. Queue 역할을 하는 놈한테 JobQueue라고 짓는 건 이상하지 않다.

마치면서

  • 좋은 이름을 선택하려면 설명 능력이 뛰어나야 하고 문화적인 배경이 같아야 한다.
  • 좋은 이름을 선택하는 능력은 교육의 문제다.
  • 사람들이 이름을 바꾸지 않으려는 이유는 다른 개발자가 반대할까 두려워서이다. 하지만 좋은 이름으로 바꿔주면 오히려 고맙다.
  • 개발자 대다수는 자기가 짠 클래스 이름과 메소드 이름을 모두 암기하지 못한다. 그러니 표나 자료 구조처럼 읽히는 코드를 짜는 데만 집중해야 마땅하다.
반응형