[Clean Code] 2. 의미있는 이름

들어가며

책의 실질적인 도입부이면서 굉장히 중요한 내용이다. 당연한 이야기가 써있겠거니 했지만 실제로 읽어보니 굉장히 많은 부분에서 공감을 했으며 동시에 반성을 하게되었다.

아래 정리된 부분은 나중에 빠르게 기억하기 위해 정리한 부분임으로 상세한 내용은 꼭 책을 통해서 읽어보길 바란다.


의미있는 이름

의도를 분명히 하라

의도가 드러나는 이름을 사용하면 코드의 이해와 변경이 쉬워진다.

잘못된 네이밍 예

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_VAULE] == FLAGGED){
			list1.add(x);
		}
	}
	return flaggedCells;
}

코드의 단순성은 변하지 않았다. 단순히 이름만 고쳤는데도 함수가 하는 일을 이해하기 쉬워졌다. 바로 이것이 좋은 이름이 주는 위력이다.

그릇된 정보를 피하라

프로그래머는 코드에 그릇된 단서를 남겨서는 안된다.

  • 여러 의미를 갖는 약어(hp, aix,sco..)를 사용하면 안된다.
  • 실제 List 객체가 아닌데도 accountList라고 명명하면 안된다. (대신에 accounts로 명명한다.)
  • 비슷한 이름을 사용하지 않도록 주의한다. (ABCControllerForHandlingOfString, ABCControllerForStorageOfString와 같이 서로 구분되지 않는 명명은 피한다.)
  • 일관성이 떨어지는 표기법 또한 그릇된 정보임으로 사용하면 안된다.

의미있게 구분하라

  • 컴파일러나 인터프리터의 구문 테스트를 통과하고자 철자를 마음대로 바꾸면 안된다. (ex. klass)
  • 연속된 숫자나 불용어(noise word)를 추가하는 방식은 적절하지 못하다. (ex. v1, v2, v3..)
  • Product 라는 클래스가 있을 때 다른 클래스를 ProductInfo 혹은 ProductData라고 부른다면 개념을 구분하지 않은채 이름만 달리한 경우다. (단순히 a, the를 네이밍에 사용하지 말라는 것이 아니라 단순히 zork이란 이름이 있어서 theZork이라는 이름을 지어서는 안된다는 것이다.)
  • 결국 읽는 사람이 이름만 읽고도 차이를 알도록 이름을 지어야 한다.

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

  • 일반적으로 읽을 수 있고 발음할 수 있는 단어를 사용해라. 예를들어 시간을 나타내는 변수로 startYmdhms 보다는 startTimestamp 라는 변수를 사용하는 것이 더 좋다.

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

  • 이름의 길이는 범위 크기에 비례해야 한다. 로컬 변수나 for/if에서만 사용되는 변수는 짧은 이름을 사용해도 괜찮지만, 전역적으로 사용하는 변수는 IDE로 검색할 수 있도록 충분히 길고 유니크 해야한다.

잘못된 예

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

코드 수정

int realDaysPerIdealDay=4;
final int WORK_DAYS_PER_WEEK = 5; 
int sum = 0; 
for (int j=0; j< NUMBER_OF_TASKS; j++){
	int realTaskDays = taskEstimate[j] * realDaysPerIdealDay; 
	int realTaskWeek = (realTaskDays / WORK_DAYS_PER_WEEK);
	sum += realTaskWeek;
}

위 코드에서 sum이 유용하지는 않으나 최소한 검색이 가능하다. 하지만 WORK_DAYS_PER_WEEK이 얼마나 검색하기 쉬울지 생각해봐라. 그냥 5가 들어간다면 5가 들어가는 이름 모두를 찾은 후 의미를 분석해 원하는 상수를 가려내야 한다

인코딩을 피하라

이름에 불필요한 인코딩을 하는 경우 또 하나의 허들이 생긴다.

  • 헝가리식 표기법 : 이름 길이가 제한된 옛날에 어쩔 수 없이 사용하던 방식이다. 요즘 나오는 프로그래밍 언어는 타입을 지원함으로 컴파일러가 타입을 기억하고 강제한다. 따라서 사용할 필요 없다.
  • 멤버 변수 접두어 : 예전에는 ’m_' 이라는 단어를 붙임으로써 멤버 변수임을 표현했다. 하지만 요즘은 접두어를 사용할 필요 없다.
  • 인터페이스 클래스와 구현 클래스 : 인터페이스와 그를 구현하는 클래스 사이에 네이밍을 어떻게 할 것인가? 인터페이스의 이름은 IShapeFactory 보다 ShapeFactory가 더 낫고 클래스 이름은 일반적으로 ShapeFactoryImp 이 사용된다.

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

일반적으로 프로그래머들은 아주 똑똑하다. 때때로 똑똑한 사람은 자신의 정신적 능력을 과시하고 싶어한다. r이라는 변수가 호스트와 프로토콜을 제외한 소문자 URL이라는 사실을 언제나 기억한다면 똑똑한 사람이다. 하지만 전문가 프로그래머는 명로함이 최고라는 사실을 이해한다.

클래스 이름

클래스 이름과 객체 이름은 명사나 명사구가 적합하다. Customer, WikiPage, Account, AddressParser 등이 좋은 예다. 하지만 Manager, Processor, Data, Info와 같이 특정한 의미를 지니는 단어를 피하고 동사를 사용하지 않아야 한다.

메서드 이름

메서드 이름은 동사나 동사구가 적합하다. postPayment, deletePage, save 등이 좋은 예이다. 접근자Accessor, 변경자Mutator, 조건자Predicate는 javabean표준에 따라 값에 get, set, is를 붙인다.

생성자를 중복 정의할 때 정적 팩토리 메서드를 사용한다. 메서드는 인수를 설명하는 이름을 사용한다. (생성자 사용을 제한하려면 해당 생성자를 private로 선언한다.)

// 안 좋은 예 
Complex fulcrumPoint = new Complex(23.0)

// 좋은 예 
Complex fulcrumPoint = Complex.fromRealNumber(23.0)

기발한 이름은 피하라

  • 기발함은 그 순간만 기억난다.

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

  • 똑같은 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다. 어느 클래스에서 어느 이름을 썼는지 기억하기 어렵다.
  • 동일 코드 기반에 controller, manager, driver를 섞어 쓰면 혼란스럽다. DeviceManager와 ProtocolController는 무엇이 다른가? 왜 둘 다 Controller 혹은 Manager가 아닌가?

말장난을 하지 마라

다른 개념에 같은 단어를 사용한다면 그것은 말장난에 지나지 않는다. 한 단어를 여러 의미로 사용하지 마라.

class Calculator<T>{
... 
T add(T t1, T t2){ ...}
}
class ObjectHolder<T>{
	List<T> holder;
	.... 
	void add(T t1){ holder.add(t1); ...}
}

위 두개의 객체는 동일한 add를 사용하면서 다른 의미로 사용하고 있다. 하지만 일관성을 유지하기 위해서는 ObjectHolder의 객체는 insert, append 등으로 변경하는것이 적당하다.

프로그래밍 영역에서 가져온 이름을 사용하라

코드를 읽는 사람도 프로그래머라는 사실을 명심한다. 따라서 전산용어, 알고리즘 이름, 패턴 이름, 수학 용어등을 사용해도 괜찮다. 모든 이름을 도메인에서 가져오는 정책은 현명하지 못하다.

도메인에서 가져온 이름을 사용하라

프로그래머 용어가 없다면 도메인 언어를 사용하도록 한다.

의미 있는 맥락을 추가하라

state라는 변수가 firstName, lastName, street, houseNumber 등과 같이 사용되면 주소의 의미가 포함된다는 것을 알 수 있지만 state가 혼자 사용된다면 상태의 의미로도 많이 사용된다. 이 경우 접두사를 추가해 addrState라고 쓰면 맥락이 분명해진다. (물론 Address 라는 객체를 만들어서 그 안에서 사용하면 더 명확해진다.)

맥락이 불분명한 변수

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){
		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 thereAreNoLetter(){
		number = "no";
        verb = "are";
        pluralModifier = "s";
	}
}

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

불필요한 맥락을 없애라

고급 휘발유 충전소(Gas Station Deluxe)라는 앱을 만든다고 가정하자. 모든 클래스 이름을 GSD로 시작하겠다는 생각은 전혀 바람직하지 못하다.

일반적으로 짧은 이름기 긴 이름보다 좋다. 단 의미가 분명한 경우에 한해서다. 이름에 불필요한 맥락을 추가하지 않도록 주의한다. (GSDAccountAddress 라는 이름은 중복이거나 부적절하다. 이는 Address라는 클래스 이름으로 적합하다.)


결론

가끔 오랫동안 관리되고 있지 않은 레거시 코드를 보게되는 경우가 있다. 이때 네이밍 룰이 이상한 경우 굉장히 코드 로직 분석에 어려움을 겪게된다. IDE가 객체들 간에 강력한 링크 기능을 제공한다고 하더라도 명확하지 않은 코드는 네이밍 룰이 아닌 실제 로직을 모두 확인해야 그 네이밍의 의미를 이해하게 되는 경우가 있다.

이를 피하기 위해선 코드를 작성할 때 의식적으로 독자(다른 프로그래머)의 입장에서 코드를 읽어봐야하며 또한 코드리뷰어가 충분한 관심을 가지고 리뷰를 해주어야 할 것이다. 그리고 그것을 위해서는 이를 충분히 숙지하고 충분한 개발 기간이 보장되어야 할 것이다.