2장. 의미 있는 이름

프라이마리모·2023년 11월 16일

Clean Code

목록 보기
1/15
post-thumbnail

의도를 분명하게 밝혀라

의도가 드러나는 이름을 사용하면 코드의 이해와 변경이 쉬워진다.
아래 예제는 지뢰찾기 게임을 하는 코드이다.
게임판에서 각 칸은 단순 배열로 표현하고, 배열에서 0번째 값은 칸 상태를 뜻한다. 값 4는 깃발이 꽂힌 상태를 가리킨다.
같은 역할을 수행하는데도 이름을 어떻게 붙이느냐에 따라 함수가 하는 일을 이해하기 쉬워진다.

//의도가 보이지 않는 이름(사전정보없이는 의도 파악 어려움)
public List<int[]> getThem() {
	List<int[]> list1 = new ArrayList<int[]>();
    for (int[] x : theList)
    	if(x[0] == 4)
        	list1.add(x);
    return list1;
}

//의도가 보이는 이름(이름으로 의도 파악 가능)
public List<int[]> getFlaggedCells() {
	List<int[]> flaggedCells = new ArrayList<int[]>();
    for (int[] cell : gameBoard)
    	if(cell[STATUS_VALUE] == FLAGGED)
        	flaggedCells.add(cell);
    return flaggedCells;
}

//이름으로 의도파악 + 직관적으로 코드 분석 가능
public List<Cell> getFlaggedCells() {
	List<Cell> flaggedCells = new ArrayList<int[]>();
    for (Cell cell : gameBoard)
    	if(cell.isFlagged())	//명시적인 함수를 사용해 FLAGGED상수 숨김 처리
        	flaggedCells.add(cell);
    return flaggedCells;
}

그릇된 정보를 피하라

  • 나름 널리 쓰이는 의미가 있는 단어를 다른 단어로 사용하면 안된다.
    ex) hp, aix, sco는 변수 이름으로 부적합 (유닉스 플랫폼, 유닉스 변종을 뜻함)
  • 여러 계정을 그룹으로 묶을 때, 실제 List가 아니라면 accountList라 명명하지 않는다.
    ex) accountGroup, Accounts 등으로 명명
  • 서로 흡사한 이름을 사용하지 않는다.
  • 유사한 개념은 유사한 표기법을 사용한다.

의미 있게 구분하라

컴파일러나 인터프리터만 통과하려는 생각으로 구현하는 코드는 바람직하지 않다. 컴파일러를 통과할지라도 연속된 숫자를 덧붙이거나 불용어를 추가하는 방식은 적절하지 못하다.
이름이 달라야 한다면 의미도 달라져야 한다.
읽는 사람이 차이를 알도록 이름을 지어야 한다.

//잘못된 이름 작성법(의미를 알 수 없는 연속된 숫자 사용)
public static void copyChars(char a1[], char a2[]) {
	for(int i = 0; i < a1.length; i++) {
    	a2[i] = a1[i];
    }
}

//올바른 작명의 작성법
public static void copyChars(char source[], char destination[]) {
	for(int i = 0; i < source.length; i++) {
    	destination[i] = source[i];
    }
}

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

//발음하기 어려운 이름의 코드
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";
    /* ... */
}

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

문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다. 상수나 한 자리수의 문자는 검색으로 찾아내기 쉽지 않아 버그 수정 역시 어려워질 수 있다.
이름 길이는 범위 크기에 비례해야 한다.

//짧게 작성된 코드
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;
}

인코딩을 피하라

유형이나 범위 정보까지 인코딩에 넣으면 이름을 해독하기 어려워진다.

헝가리식 표기법

초창기 베이식은 글자 하나에 숫자 하나만 허용했으나, 헝가리식 표기법은 기존 표기법을 완전히 개선했다. 그러나 요즘의 프로그래밍언어는 훨씬 많은 타입을 지원하며, 컴파일러가 타입을 기억하고 강제한다.
클래스와 함수는 점차 작아지는 추세로, 변수를 선언한 위치와 사용하는 위치가 멀지 않다.

멤버변수 접두어

클래스와 함수는 접두어가 필요없을 정도로 작아야 하며, 멤버 변수를 다른 색상으로 표시하거나 눈에 띄게 보여주는 IDE를 사용해야한다. 접두어를 사용할 경우 옛날에 작성한 구닥다리 코드라는 징표가 되어버린다.

인터페이스 클래스와 구현 클래스

때로는 인코딩이 필요한 경우도 있다. 예를 들어 도형을 생성하는 ABSTRACT FACTORY를 구현한다고 할 때, 이 팩토리는 인터페이스 클래스이며, 구현은 구체 클래스에서 한다. 이 때 두 클래스 명을 지을 때, 인터페이스 클래스 이름과 구현 클래스 이름 중 하나를 인코딩 해야한다면 구현 클래스명을 인코딩하는 편이 좋다.

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

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

클래스 이름, 객체 이름 : 명사나 명사구가 적합

  • 좋은 예 : Customer, WikiPage, Account, AddressParser
  • 피해야 할 단어 : Manager, Processor, Data, Info, 동사

메서드 이름 : 동사나 동사구가 적합

  • 좋은 예 : postPayment, deletePage, save
  • 접근자, 변경자, 조건자는 표준에 따라 값 앞에 get, set, is를 붙인다.
  • 생성자를 중복정의할 때는 정적 팩토리 메서드를 사용한다.
    //메서드는 인수를 설명하는 이름을 사용 > 알아보기 쉽다.
    Complex fulcrumPoint = Complex.FormRealNumber(23.0);  
    Complex fulcrumPoint = new Complex(23.0);

기발한 이름은 피하라

의도를 분명하고 솔직하게 표현하라.

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

일관성 있는 어휘를 사용하라.

말장난을 하지 마라

  • 한 단어를 두 가지 목적으로 사용하지 마라.
  • ex) 지금까지 구현한 add 메서드는 모두가 기존 값 두 개를 더하거나 이어서 새로운 값을 만든다.
    새로 작성하는 메서드는 집합에 값 하나를 추가한다. 이 메서드를 기존 add 메서드와 맥락이 다르므로 단순히 일관성을 위해서 add를 사용하면 말장난이 된다.
    이 메서드는 insert나 append라는 이름이 적당하다.


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

코드를 읽을 사람도 프로그래머이므로 기술 개념에는 기술 이름이 가장 적합한 선택이다.

문제 영역에서 가져온 이름을 사용하라

문제 영역 개념과 관련이 깊은 코드라면 문제 영역에서 이름을 가져와야 한다.

의미 있는 맥락을 추가하라

스스로 의미가 분명한 이름이 없지 않다. 하지만 대다수 이름은 그렇지 못하므로 클래스, 함수, 이름 공간에 넣어 맥락을 부여한다. 모든 방법이 실패하면 마지막 수단으로 접두어를 붙인다.

아래 예제를 살펴보면, 함수 이름은 맥락 일부만 제공하며, 알고리즘이 나머지 맥락을 제공한다. 함수를 끝까지 읽어보고 나서야 number, verb, pluralModifier라는 변수 세 개가 '통계 추측' 메시지에 사용된다는 사실이 드러난다.

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

위 예제는 함수가 상당히 길고 세 변수를 함수 전반에서 사용한다.

아래는 위 예제를 리팩토링하였다.

  • 함수를 작은 조각으로 쪼개기 위해 GuessStatisticsMessage 클래스 생성
  • 세 변수를 GuessStatisticsMessage 클래스에 위치(공통 사용)
  • 목적에 맞게 함수 쪼개기
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";
    }
}

불필요한 맥락을 없애라

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

  • ex) '고급 휘발유 충전소(Gas Station Deluxe)'라는 애플리케이션 작성 시 모든 클래스 이름을 GSD로 시작할 필요는 없다.
  • ex) accountAddress와 customerAddress는 클래스 인스턴스로 적합하다.
    Address는 클래스 이름으로 적합하다. 포트주소, MAC주소, 웹주소를 구분해야한다면 PostalAddress, MAC, URI도 적합하다.
    이름은 명확하게 작성해야 한다.
profile
개발공부 요약노트

0개의 댓글