Hack my life

[Clean Code] 의미 있는 이름-프로그래밍 네이밍방법 본문

개발

[Clean Code] 의미 있는 이름-프로그래밍 네이밍방법

sjsjsj1246 2019. 2. 17. 03:21

*이 글은 책 Clean Code를 기반으로 작성하였습니다.

*Java 문법을 알고 있어야 이해하기 쉽습니다.



1. 프로그래밍 명명 규칙을 배워야 하는 이유


  • 프로그래밍을 할 때 이름이 굉장히 많이 쓰인다. 변수에도 이름을 붙이고, 함수, 임수, 클래스, 패키지 에도 이름이 필요하다. 심지어 소스파일, 소스파일이 담긴 디렉터리에도 이름이 필요하다. 
  • 이렇게 이름이 많이 쓰이므로 이름을 잘 지으면 프로그래밍을 할 때 아주 편하다.



2. 의도를 분명히 밝혀라


  • 조금 시간이 들더라도 이름을 지을때는 그 것의 존재 의도, 기능, 사용법이 잘 드러나게 지어야 한다.


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


d 라는 이름은 그 자체만 봤을 때 아무 의미도 드러나지 않는다. 다음과 같이 바꿀 수 있다.


1
2
3
4
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

cs


  • 의도가 드러나는 이름을 사용하면 코드 이해와 변경이 쉬워진다. 다음 코드를 보자
1
2
3
4
5
6
7
8
9
public List<int[]> getThem() {
    List<int[]> list1 = new ArrayList<int[]>();
    for (int[] x : theList) {
        if (x[0== 4) {
            list1.add(x);
        }
    }
    return list1;
}
cs


코드의 공백과 들여쓰기도 적당하고 변수, 상수도 적지만 코드가 하는 일을 짐잦하기 어렵다.

문제는 코드의 단순성이 아니라 코드의 함충성이다.

이름들의 의도가 불분명해서 코드의 맥락이 없다. 위 코드는 암암리에 독자가 다음과 같은 정보를 안다고 가정한다


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

지뢰 찾기 게임을 만든다고 가정하자, theList는 게임판이다. 게임판에서 각 칸은 단순 배열로 표현한다. 

배열에서 0번째 값은 칸의 상태를 뜻한다. 값 4는 깃발이 꽂힌 상태를 가리킨다.

상황을 알았으니 이름을 다시 써보자.


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


코드의 구조는 동일하나 위 코드보다 훨씬 명확해졌다

한걸음 나아가 int 배열 대신 칸을 클래스로 만들어주고 isFlagged라는 좀 더 명시적인 함수를 사용해 FLAGGED라는 상수를 감춰도 좋을 것 같다.


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


처음보다 코드를 이해하기가 굉장히 쉬워졌다.



3. 그릇된 정보를 피하라

  • 프로그래머에게 특수한 의미를 가지는 단어(List등)는 실제 컨테이너가 List가 아닌 이상 accountList와 같이 변수명에 붙이지 말자. 차라리 accountGroup, bunchOfAccounts, accounts등으로 명명하자, 
  • 중의적으로 해석될 수 있는 이름은 짓지 않는다. 직각삼각형의 빗변을 구할 때 hypotenuse를 hp로 줄이는 것이 훌륭한 약어로 보일지라도 hp라는 이름은 독자에게 그릇된 정보를 제공한다.
  • 서로 흡사한 이름을 사용하지 않도록 조심한다.


4. 의미있게 구분하라
  • 숫자 하나로 구분하지 말자 a1, a2, a3... 등등
  • 의미없는 구분을 하지말자: 
    • Name VS NameString
    • getActiveAccount() VS getActiveAccounts() VS getActiveAccountInfo()
    • money VS moneyAmount
    • message VS theMessage


5. 발음하기 쉬운 이름을 사용하라
  • 프로그래밍은 사회적 활동이다. 발음하기 어려운 이름은 본인의 사고에도 방해가 되고 사람들과 협업할 때는 더욱 그렇다.
1
2
3
4
5
6
7
// Bad
class DtaRcrd102 {
    private Date genymdhms;
    private Date modymdhms;
    private final String pszqint = "102";
    /* ... */
};
cs

1
2
3
4
5
6
7
// Good
class Customer {
    private Date generationTimestamp;
    private Date modificationTimestamp;
    private final String recordId = "102";
    /* ... */
};
cs



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

  • 상수를 여러 곳에서 사용한다면 이름을 붙여 사용한다. 디버깅을 할때 편하다. 5라는 상수를 썼을때 5를 검색하며 디버깅 할 모습을 상상해라
  • 변수의 이름의 길이는 변수의 범위에 비례해서 길어진다. 검색하기 편하기 때문


7. 인코딩(변수에 부가 정보를 덧붙여 표기하는것)을 피해라
  • 헝가리안 표기법을 피하라, 요즘은 IDE의 기능이 굉장히 좋아졌기 때문에 타입을 유추할수 있는 표기법이 필요가 없어졌다.
  • 멤버 면수에 접두어를 붙이지 않는다.
1
2
3
4
5
6
7
//Bad
public class Part {
    private String m_dsc;
    void setName(String name) {
        m_dsc = name;
    }
}
cs

1
2
3
4
5
6
7
//Good
public class Part {
    private String description;
    void setDescriptione(String description) {
        this.description = description;
    }
}
cs
  • 인터페이스 이름에 접두어를 붙이지 않는다. IShapeFactory VS ShapeFactory



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

  • 독자가 머리속으로 한번 더 생각해 변환해야 할만한 변수명을 쓰지 말라.
  • 한글자 변수이름은 문제가 있다(루프에서 반복문 횟수를 세는 변수 i,j,k 제외)
  • 똑똑한 프로그래머와 전문가 프로그래머를 나누는 기준 한가지는 Clarity(명료함)이다.


9. 클래스 이름
  • 명사 혹은 명사구를 사용하라.(Customer, WikiPage, Account, AddressParser)
  • Manager, Processor, Data, Info와 같은 단어는 피하자
  • 동사는 사용하지 않는다.


10. 메서드 이름
  • 동사 혹은 동사구를 사용하라.(postPayment, deletePayment, deletePage, save 등)
  • 접근자, 변경자, 조건자는 get, set, is로 시작하자.
1
2
3
String name = employee.getName();
customer.setName("mike");
if(paycheck.isPosted());...
cs

  • 생성자를 오버로드할 경우 정적 팩토리 메서드를 사용하고 해당 생성자를 private으로 선언한다.



11. 기발한 이름은 피하라

  • 특정 문화에서만 사용되는 재미있는 이름보다 의도를 분명히 표현하는 이름을 사용하라
    • HolyHandGrenade → DeleteItems
    • whack() → kill()


12. 한 개념에 한 단어를 사용하라
  • 똑같은 기능의 메서드를 어떤 클래스에서는 get 어떤 클래스에서는 retrieve, fetch등으로 제각각 부르면 굉장히 혼란스럽다.
  • controller, manager, driver 도 마찬가지이다. 따라서 메서드의 이름은 일관적이어야 한다.

13. 말장난을 하지마라
  • add라는 의미가 기존 두 값을 더하거나 이러서 새로운 값을 만드는데 쓰였다고 하자. 새로 작성하는 메서드는 집합에 값 하나를 추가한다. 일관성을 고려한답시고 이 메서드를 add라고 부르면 안된다.
  • 즉 같은 맥락이 아닌데도 일관성을 고려한다고 같은 단어를 선택하면 안된다


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

  • 코드를 읽는 사람은 프로그래머다. 따라서 프로그래머라면 알만한 용어들은 사용해도 좋다.
  • 알고리즘 이름, 전산 용어, 수학 용어, 등등
    • JobQueue, AccountVisitor(Visitor pattern)


15. 문제 영역에서 가져온 이름을 사용하라
  • 적절한 프로그래머 용어가 없거나 문제영역과 관련이 깊은 용어의 경우 문제 영역 용어를 사용하자.



16. 의미 있는 맥락을 추가하라

  • 클래스, 함수, namespace등으로 감싸서 맥락(Context)을 표현하라
  • 그래도 불분명하다면 접두어를 사용하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Bad
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);
}
cs


변수들의 의미와 전체적인 맥락이 잘 들어오지 않는다. Address라는 클래스를 만들어서 감싸보자.


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
// Good
public class GuessStatisticsMessage {
    private String number;
    private String verb;
    private String pluralModifier;
 
    public String make(char candidate, int count) {
        createPluralDependentMessageParts(count);
        return String.format("There %s %s %s%s", verb, number, candidate, pluralModifier );
    }
 
    private void createPluralDependentMessageParts(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";
    }
}
cs



17. 불필요한 맥락을 없애라

  • Gas Station Delux 이라는 어플리케이션을 작성한다고 해서 클래스 이름의 앞에 GSD를 붙이지는 말자. G를 입력하고 자동완성을 누를 경우 모든 클래스가 나타나는 등 효율적이지 못하다.
  • 위 a처럼 접두어를 붙이는 것은 모듈의 재사용 관점에서도 좋지 못하다. 재사용하려면 이름을 바꿔야 한다.(eg, GSDAccountAddress 대신 Address라고만 해도 충분하다.)