2. 의미 있는 이름

Posted by yunki kim on January 2, 2022

  소프트웨어에서 이름을 작성하는 경우는 아주 많다. 변수, 함수, 패키지, 클래스 등 아주 많은 곳에 이름을 붙여야 한다. 따라서 이름을 잘 지어야 한다.

  이름을 잘 짓는 규칙은 다음과 같다. 

  1. 의도를 분명히 밝혀라

    이름에 의도를 정확히 명시해야 한다. 좋은 이름을 짓기 위해서는 많은 시간이 필요하지만 이로 인해 절약되는 시간이 더 많다. 그러므로 이름을 보고 더 좋은 이름이 떠오른다면 개선해야 한다. 

    다음과 같은 코드가 있다고 해보자. 이 코드의 의도는 뭐일까?

1
2
3
4
5
6
7
8
9
public List<int[]> getThem() {
    List<int []> list1 = new ArrayList<>();
    for (int[] x: theList) {
        if (x[0== 4) {
            list1.add(x);
        }
    }
    return list1;
}
cs

  이 코드는 비록 짧지만 list1이 뭐에 대한 리스트인지 theList는 어떤 리스트인지 전혀 짐작이 되지 않는다. 또 한 왜 x의 0번 값이에 조건을 거는지도 파악할 수 없다. 

  이 코드는 지뢰찾기 게임의 일부라 가정해 보자. 그 후 theList는 게임판이고 게임판이 배열로 표현된다고 하자. 값 4는 깃발이 꽂힌 상태고 배열에서 0번째 값은 상태를 뜻한다 해보자. 그러면 위의 불분명한 코드를 다음과 같이 개선할 수 있다.

1
2
3
4
5
6
7
8
9
public List<int[]> getFlaggedCells() {
    List<int []> flaggedCells = new ArrayList<>();
    for (int[] x: gameBoard) {
        if (cell[STATUS_VALUE] == FLAGGED) {
            flaggedCells.add(x);
        }
    }
    return flaggedCells;
}
cs

  이 코드의 구조는 전혀 변하지 않았고 단지 의도가 명확한 이름을 사용했을 뿐이다. 그럼에도 코드가 더 명확해졌다.

  여기서 한걸음 더 나아가 칸을 int 배열로 표현하는 것이 아닌 간단한 클래스로 바꾸고 조건문의 조건을 메서드로 바꾼다면 코드는 더 명확해 진다.

1
2
3
4
5
6
7
8
9
public List<Cell> getFlaggedCells() {
    List<Cell> flaggedCells = new ArrayList<>();
    for (Cell cell: gameBoard) {
        if (cell.isFlagged()) {
            flaggedCells.add(cell);
        }
    }
    return flaggedCells;
}
cs

  2. 그릇된 정보를 피해라

    코드에 그릇된 정보를 남기지 말라. 그릇된 정보를 남기면 이름 하나만 봤을때는 의미가 명확해도 전체를 봤을때는 혼동이 올 수 있다. 심지어는 이름 하나만 보더라도 혼동이 올 수 있다.

    변수 이름이 hp라고 해보자. hp는 유닉스 플랫폼이나 유닉스 변종을 가리키는 이름이기도 하지만 직삼각형의 빗변(hypotenuse)을 가키리는 약어 이기도 하다.

    특수한 의미를 가지는 용어를 남발해서도 안된다. 여러 계정을 그룹으로 묶을때 그 변수의 이름을 accountList라 했다 하자. 여기서 List는 프로그래머에게 특수한 의미를 갖는다. 계정을 담는 컨테이너가 실제 List가 아니라면 대신에 accountGroup 또는 accounts 등으로 명명해야 한다.

    서로 흡사한 이름을 사용하지 말라. 한 모듈에서 XYZControllerForEfficientHandlingOfStrings라는 이름을 사용하고 다른 모듈에서 XYZControllerForEfficientStorageOfStrings라는 이름을 사용한다면 둘의 차이를 명확히 분간할 수 없다.

    유사한 개념은 유사한 효기법을 사용하되 명확해야 한다. 

  3. 의미 있게 구분하라

    컴파일러나 인터프리터만 통과할려고 하지 말라. 예를 들어 동일한 범위 내에서 같은 이름을 사용하지 못하기 때문에 하나를 의미를 파악할 수 없는 다른 이름으로 바꾸는 것을 하지 말라. 컴파일러를 통과하기 위해서 the, a 같은 불용어(noise word)를 사용하거나 의미 없는 숫자를 덧붙이지 말라. 이름이 달라야 한다면 의미도 달라야 한다. 불용어와 의미 없는 숫자는 아무런 정보도 제공하지 못한다.

1
2
3
4
5
public static void copyChars(char a1[], char a2[]) {
    for (int i = 0; i < a1.length; i++) {
        a2[i] = a1[i];
    }
cs

    위 예시의 파라미터의 이름은 아무런 의미를 갖지 않는다. a1대신 source를 a2대신 destination을 사용하는 것이 더 명확하다.

    불용어는 중복이다. NameString이 Name보다 나은 점은 없다. Customer역시 CustomerObject보다 나은 점이 없다. 그저 중복만 생길 뿐이다.

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

    개발은 혼자 하는 것이 아니다. 여러 사람과 소통을 해야 한다. 이때 발음하기 어려운 이름을 사용한다면 사람바다 발음하는 방식이 다를거고 신입한테 발음하는 법을 알려줘야한다. 아래 예제를 보자.

1
2
3
4
5
6
7
8
9
10
11
class DtaRcrd102 {
    private Date genymdhms;
    private Date modymdhms;
    private final String pszqint = "102";
}
 
class Customer {
    private Date generationTimestamp;
    private Date modificationTimestamp;
    private final String recordId = "102";
}
cs

    두 클래스는 같은 기능을 한다. 어느 클래스가 소통하기 편할까?

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

      문자 하나를 사용하는 이름과 상수를 사용하지 말라. 이들은 텍스트 코드에서 쉽게 눈에 띄지 않는다. MAX_CLASSES_PER_STUDENT가 상수 7일때 단순 7과 MAX_CLASSES_PER_STUDENT 중 어느것이 찾기 쉬운가? 어느것이 더 이해하기 쉬운가? 문자 하나도 마찬가지다. 상수와 문자 하나를 검색하면 수 많은 코드들이 검색된다. 따라서 잘못된 검색을 이용해 버그를 야기할 수 있다.

    6. 인코딩을 피하라

      유형이나 범위 정보를 인코딩에 넣으면 이름을 해석하기 어려워 진다. 새로운 개발자가 익혀야 하는 코드는 아주 많다. 그 상황에서 인코딩 언어 까지 익히는 것은 비합리 적이다. 과거에는 이름 길이에 제한이 존재했고 예전 윈도 C API의 경우 컴파일러가 타입을 점검하지 않았다. 따라서 이런 정보를 이름에 인코딩 해야 했다.

      하지만 요즘에는 상황이 많이 다르다. 언어는 더 많은 타입을 지원하고 컴파일러가 타입을 기억한다. 또 한 클래스는 작아지고 있다. 자바는 강한 타입(strongly-type)이고 IDE는 코드를 컴파일 하지 않아도 타입 오류를 감지할 수 있다. 따라서 여러 인코딩 방식은방해만 된다.

 

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

  코드를 읽을때 변수의 이름을 자신이 아는 이름으로 변환해야 한다면 그 변수 이름을 적절치 못하다. 이름은 최대한 명료해야 한다.

  문자 하나만 사용하는 변수 역시 사용하지 말라. 물론 전통적으로 루프에서 반복 횟수 카운팅을 세는 변수는 하나의 문자만 사용하고 있기 때문에 루프가 짧은 경우는 괜찮다. 

  똑똑한 프로그래머와 전문가 프로그래머의 차이는 전문가 프로그래머는 명료함이 최고라는 사실을 알고 있다는 것이다. 전문가 프로그래머는 자신의 능력을 좋은 방향으로 사용해 남들이 이해하는 코드를 내놓는다.

 

클래스 이름

  클래스 이름과 객체 이름은 명사 또는 명사구를 사용하라. Customer, WikiPage같은 명료한 명사가 좋다. Data, Info등은 피하라.

 

메서드 이름

  메서드 이름은 동사나 동사구가 적합하다. postPayment, deletePage가 그 예시다. 접근자(accessor), 변경자(mutator), 조건자(predicate)는 javabean표준에 따라 값 앞에 get, set, is를 붙인다.

  생성자를 오버로드 할때는 정적 팩토리 메서드를 사용하라. 

 

기발한 이름은 피하라

  이름을 재미있게 짓기 위해 유머를 사용한다거나 특정 지역에서만 사용하는 속어를 이름으로 사용하지 말라. 이름은 간단 명료해야 한다.

 

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

  추상적인 개념 하나에는 반드시 하나의 단어만을 사용하라. 똑같은 메서드를 클래스마다 fetch, get 등으로 다르게 표기한다면 혼란이 온다. 기억하기도 어렵다. 

  최신 IDE는 문맥에 맞는 단서를 제공하지만 주석까지 보여주지는 않는다. 따라서 주석 없이도 알아볼 수 있게 독자적이고 일관적인 메서드 이름을 사용하라.

 

말장난을 하지 마라

  한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용하는 것은 말장난이다. 예를 들어 기존 코드에선 add를 두 값을 더하는 메서드에 사용했다 하자. 하지만 새로운 코드에서 add를 집합 추가에 사용한다면 코드에 혼란이 온다.

  프로그래머는 코드를 최대한 쉽게 작성해야 한다. 대충 훑어봐도 이해할 수 있는 코드가 좋은 코드이다. 

 

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

  코드는 개발자가 읽는다. 따라서 전산 용어, 알고리즘 이름 등을 사용해도 된다. 모든 이름을 도메인에서 가져오는 것은 좋지 않다. 같은 개념을 다른 이름으로 이해하는 개발자들은 매번 그 의미를 물어야 한다. 예를 들어 visitor 패턴에 친숙한 프로그래머가 AccountVisitor라는 이름을 보면 어떤 생각을 할까?

 

문제 영역(도메인)에서 가져온 이름을 사용하라

  적절한 프로그래밍 용어가 없다면 도메인에서 이름을 가져와라. 그러면 코드를 유지보수 하는 프로그래머가 분야 전문가에게 의미를 물어 파악할 수 있다.

  우수한 프로그래머와 설계자는 해법 영역과 도메인을 구분할 줄 알아야 한다. 문제 영역 개념과 관련이 깊은 코드면 문제 영역에서 이름을 가져와야 한다.

 

의미 있는 맥락을 추가하라

  대다수의 이름은 스스로 분명하지 못하다. 그래서 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 예를 들어 firstName, lastName, street, houseNumber, city, state, zipcode가 같이 있다면 한번 보는것 만으로 주소라는 사실을 알게 된다. 하지만 state하나만 존재한다면 이것이 주소라는 것을 알아챌 수 있을까?

  이럴때는 접두사 addr을 추가해 addrState라 하거나 Addresss라는 클래스를 생성하면 더 좋다.

  아래와 같은 코드가 있다고 하자. 아래의 코드는 함수 이름이 맥락의 일부만 제공하고 나머지 맥락은 알고리즘이 제공한다. 따라서 함수 전체를 읽고 나서야 number, verb, pluralModifier라는 변수 세 개가 '통계 추측(guess statistics)'메시지에 사용된다는 것을 알 수 있다. 또 한 함수가 길다는 것 역시 문제가 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
    );
    System.out.println(guessMessage);
}
cs

  이 코드를 다음과 같이 GuessStatisticsMessage라를 클래스를 만들어 세 변수를 속성으로 하면 맥락을 쪼개기 쉬워지니 알고리즘이 명확해 진다.

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
39
40
class GuessStatisticsMessage {
    private String number;
    private String verb;
    private String pluralModifier;
    
    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";
    }
    
    private void createPluralDependentMessageParts(int count) {
        if (count == 0) {
            thereAreNoLetters();
        } else if (count == 1) {
            thereIsOneLetter();
        } else {
            thereAreManyLetters(count);
        }
    }
    
    public String make(char candidate, int count) {
        createPluralDependentMessageParts(count);
        return String.format("There %s %s %s%s",
            verb, number, candidate, pluralModifier);
    }
}
 
cs

 

불필요한 맥락을 없애라

  '고급 휘발유 충전소(Gas Station Deluxe)'라는 애플리케이션을 짠다고 해보자. 모든 클래스 이름을 GSD로 시작한다면 IDE에서 G를 검색할 시 모든 클래스가 열거된다. IDE는 개발자를 지원하는 도구이다. IDE를 방해하지 말자. 

  일반적으로는 짧은 이름이 긴 이름 보다 좋다. 다만 짧은 이름 일지라도 의도는 명확해아 하며 불필요한 맥락을 추가하면 안된다.

 

출처 - 클린 코드