본문 바로가기

개발/클린 코드

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

들어가면서

  • 이름을 잘 짓는 간단한 규칙을 몇 가지 소개한다

의도를 분명히 밝혀라

  • 의도가 분명한 이름이 정말로 중요하다는 사실을 거듭 강조한다.
  • 이름을 주의 깊게 살펴 더 나은 이름이 떠오르면 개선하기 바란다.
  • 변수(혹은 함수나 클래스)의 존재 이유는? 수행 기능은? 사용 방법은? 
    • 따로 주석이 필요하다면 의도를 분명히 드러내지 못했다는 말이다.
int 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);
    }
    return lsit1;
}

복잡한 문장도 없고, 공백과 들여쓰기도 적당하고, 변수는 세 개, 상수는 두 개뿐이고, 화려한 클래스나 다형메서드도 없다.

코드의 함축성이 문제다. 코드 맥락이 코드 자체에 명시적으로 드러나지 않는다.

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 배열 대신 칸을 간단한 클래스로 만들어도 되겠다.

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

isFlagged라는 명시적인 함수를 사용해, FLAGGED라는 상수를 감춰도 좋겠다.

이것이 좋은 이름이 주는 위력이라고 설명하고 있다.

 

그릇된 정보를 피하라

  • 나름대로 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해도 안 된다.
  • 예를 들어, hp, aix, sco는 변수 이름으로 적합하지 않다. 유닉스 플랫폼이나 유닉스 변종을 가리키는 이름이다.
  • 직각삼각형의 빗변(hypotenuse)을 구현할 때는 hp가 훌륭한 약어로 보일지라도 hp라는 변수는 독자에게 그릇된 정보를 제공한다.
  • 여러 계정을 그룹으로 묶을 때, 실제 List가 아니라면, accountList라 명명하지 않는다.
    • 프로그래머에게 List라는 단어는 특수한 의미다. 계정을 담는 컨테이너가 실제 List가 아니라면 프로그래머에게 그릇된 정보를 제공하는 셈이다.
  • 서로 흡사한 이름을 사용하지 않도록 주의한다.
  • 일관성이 떨어지는 표기법은 그릇된 정보다.
  • 이름으로 그릇된 정보를 제공하는 진짜 끔찍한 예가 소문자 L이나 대문자 O 변수다.
    • 소문자 L은 숫자 1처럼 보이고, 대문자 O는 숫자 0처럼 보인다.

의미 있게 구분하라

  • 연속된 숫자를 덧붙이거나, 불용어(noise word)를 추가하는 방식은 적절하지 못하다.
  • Product라는 클래스가 있다고 가정한다. 다른 클래스를 ProductInfo / ProductData라 부른다면 개념을 구분하지 않은 채 이름만 달리한 경우다. 
    • Info / Data는 a, an, the와 마찬가지로 의미가 불분명한 불용어다.
  • 요지는, zork라는 변수가 있다는 이유만으로 theZork라 이름 지어서는 안 된다는 말이다.
  • 불용어는 중복이다. 변수 이름에 variable이라는 단어는 단연코 금물이다. 표 이름에 table이라는 단어도 마찬가지다.
  • NameString이 Name보다 뭐가 나은가?
getActiveAccount();
getActiveAccounts();
getActiceAccountInfo();

이 프로젝트에 참여한 프로그래머는 어느 함수를 호출할지 어떻게 알까?

명확한 관례가 없다면, 변수 moneyAmount는 money와 구분이 안 된다. customerInfo는 customer와, accountData는 account와, theMessage는 message와 구분이 안 된다. 읽는 사람이 차이를 알도록 이름을 지어라.

 

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

저자가 아는 회사 하나는 genymdhms (generate date, year, month, day, hour, minute, second) 라는 단어를 사용했다. 우스꽝스러운 발음을 농담처럼 주고받다 보니까 재미는 있었다. 새로운 개발자가 들어오면 변수를 설명해주어야 한다.

class DtaRcrd102 {
    private Date genymdhms;
    private Date modymdhms;
    private final String pszqint = "102";
};

class Customer {
    private Date generationTimestamp;
    private Date modificationTimestamp;
    private final String recorodId = "102";
};

둘째 코드를 보아라

 

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

문자 하나를 사용하는 이름과 상수는 텍스트 코드에서 쉽게 눈에 띄지 않는다는 문제점이 있다.

  • MAX_CLASSES_PER_STUDENT는 grep으로 찾기가 쉽지만, 숫자 7은 은근히 까다롭다. 
  • e 라는 문자도 변수 이름으로 적합하지 못하다. 검색이 어려운 탓이다.

개인적으로는, 간단한 메서드에서 로컬 변수만 한 문자를 사용한다. 이름 길이는 범위 크기에 비례해야 한다. 변수나 상수를 코드 여러 곳에서 사용한다면 검색하기 쉬운 이름이 바람직하다.

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

그냥 5를 사용한다면, 5가 들어가는 이름을 모두 찾은 후 의미를 분석해 원하는 상수를 가려내야 하리라.

 

인코딩을 피하라

헝가리식 표기법

 과거에는, 윈도 C API는 헝가리식 표기법을 중요하게 여겼다. 당시에는 컴파일러가 타입을 점검하지 않았으므로 프로그래머에게 타입을 기억할 단서가 필요했다.

 요즘 나오는 프로그래밍 언어는 훨씬 많은 타입을 지원한다. 또한 컴파일러가 타입을 기억하고 강제한다. 게다가 클래스와 함수는 점차 작아지는 추세다. 즉, 변수를 선언한 위치와 사용하는 위치가 멀지 않다.

 자바 프로그래머는 변수 이름에 타입을 인코딩할 필요가 없다. 객체는 강한 타입이며, IDE는 코드를 컴파일하지 않고도 타입 오류를 감지할 정도로 발전했다. 따라서 이제는 헝가리식 표기법이나 기타 인코딩 방식이 오히려 방해가 될 뿐이다. 

 

멤버 변수 접두어

 이제는 멤버 변수에 m_ 이라는 접두어를 붙일 필요도 없다. 클래스와 함수는 접두어가 필요없을 정도로 작아야 마땅하다. 또한 멤버 변수를 다른 색상으로 표시하거나 눈에 띄게 보여주는 IDE를 사용해야 마땅하다.

 

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

때로는 인코딩이 필요한 경우도 있다. ABSTRACT FACTORY를 구현한다고 가정할때, 구현은 구체 클래스에서 한다. 이때, IShapeFactory 와 ShapeFactory? 

저자는, 인터페이스 이름은 접두어를 붙이지 않는 편이 좋다고 생각한다. 접두어 I는 주의를 흐트리고, 과도한 정보를 제공한다. 내가 다루는 클래스가 인터펭스라는 사실을 남에게 알리고 싶지 않다. 클래스 사용자는 그냥 ShapeFactory라고만 생각하면 좋겠다. 그래서 인터페이스 클래스 이름과 구현 클래스 이름 중 하나를 인코딩해야 한다면, 구현 클래스 이름을 택한다고 설명한다.

ShapeFactoryImp 혹은 CShapeFactory가 IShapeFactory보다 낫다는 말이다.

 

 

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

문자 하나만 사용자는 변수 이름은 문제가 있다. 루프에서 사용되는 i,j,k는 괜찮다. 하지만 그 이외에는, 독자가 실제 개념으로 변환해야하므로 적절하지 못하다. 전문가 프로그래머는 명료함이 최고라는 사실을 이해한다. 

 

클래스 이름

클래스 이름과 객체 이름은 명사나 명사구가 적합하다. Customer, WikiPage, Account, AddressParser 등이 적절한 예다. Manager, Processor, Data, Info 등과 같은 단어는 피하고, 동사는 사용하지 않는다.

 

메서드 이름

메서드 이름은 동사나 동사구가 적합하다. postPayment, deletePage, save 등이 좋은 예다.

생성자를 중복정의 할때는, 정적 팩토리 메서드를 사용한다. 메서드는 인수를 설명하는 이름을 사용한다. 

Complex fulcrumPoint = Complex.FromRealNumber(23.0);


Complex fulcrumPoing = new Complex(23.0);

위 코드가 아래 코드보다 좋다.

 

기발한 이름은 피하라

혼자만 알만한 이름을 사용하지 마라

 

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

추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다. 

fetch, retrieve, get / controller, manager, driver를 동일 코드 기반에 섞어 쓰면 혼란스럽다. DeviceManager , ProtocolController는 근본적으로 어떻게 다른가?

일관성 있는 어휘는 코드를 사용할 프로그래머가 반갑게 여길 산물이다.

 

말장난을 하지 마라

한 단어를 두 가지 목적으로 사용하지 마라. 다른 개념에 같은 단어를 사용한다면 그것은 말장난에 불과하다.

add라는 메서드가 있다. 같은 맥락이 아닌데도 '일관성'을 고려해 add라는 단어를 선택한다. 기존의 add 메서드는 모두가 기존 값 두개를 더하거나 이어서 새로운 값을 만든다고 가정하자. 하지만, 새로 작성하는 add는 집합에 값 하나를 추가한다. 이 메서드는 기존 add 메서드와 맥락이 다르다. 

그러므로, insert나 append라는 이름이 적당하다. 새 메서드를 add라고 부른다면 이는 말장난이다.

 

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

코드를 읽을 사람도 프로그래머다. 전산 용어, 알고리즘 이름, 패턴 이름, 수학 용어 등을 사용해도 괜찮다. VISITOR 패턴에 친숙한 프로그래머는 AccountVisitor 라는 이름을 금방 이해 한다. JobQueue를 모르는 프로그래머가 있을까? 기술 개념에는 기술 이름이 가장 적합한 선택이다. 

 

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

적절한 '프로그래밍 용어'가 없다면 문제 영역에서 이름을 가져온다. 

 

의미 있는 맥락을 추가하라

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

  예를 들어, firstName, lastName, street, houseNumber, city, state, zipcode라는 변수가 있다. 주소라는 사실을 금방 알아챌수 있지만, 어느 메서드가 state라는 변수 하나만 사용한다면? 주소의 일부라는 사실을 금방 알아챌까?

addr라는 접두어를 추가해 addrState라 쓰면 맥락이 좀 더 분명해진다. Address라는 클래스를 생성하면 더 좋다. 

 

불필요한 맥락을 없애라

'고급 휘발유 충전소' 라는 애플리케이션을 짠다고 가정하자. 모든 클래스 이름을 GSD로 시작해야겠다는 생각은 전혀 바람직하지 못하다. IDE에서 G를 입력하고 자동 완성 키를 누르면 IDE는 모든 클래스를 열거한다. 

일반적으로는 짧은 이름이 긴 이름보다 좋다. 단, 의미가 분명한 경우에 한해서다. 이름에 불필요한 맥락을 추가하지 않도록 주의한다. 

accountAddress와 customerAddress는 Address 클래스 인스턴스로는 좋은 이름이나 클래스 이름으로는 적합하지 못하다. 

Address는 클래스 이름으로 적합하다. 포트 주소, MAC 주소, 웹 주소를 구분해야 한다면 PostalAddress, MAC, URI라는 이름도 괜찮겠다. 그러면 의미가 좀 더 분명해진다. 바로 이것이 이름을 붙이는 이유가 아니던가?

 

마치면서

좋은 이름을 선택하려면 설명 능력이 뛰어나야 하고 문화적인 배경이 같아야 한다. 이것이 제일 어렵다. 좋은 이름을 선택하는 능력은 기술, 비즈니스, 관리 문제가 아니라 교육 문제다. 우리 분야 사람들이 이름 짓는 방법을 제대로 익히지 못하는 이유가 바로 여기에 있다.

암기는 요즘 나오는 도구에게 맡기고, 우리는 문장이나 문단처럼 읽히는 코드 아 니면 적어도 표나 자료 구조처럼 읽히는 코드를 짜는 데만 집중해야 마땅하다.

이름을 나름대로 바꿨다가는 누군가 질책할지도 모른다. 그렇다고, 코드를 개선하려는 노력을 중단해서는 안 된다.

'개발 > 클린 코드' 카테고리의 다른 글

클린코드 5장 - 형식 맞추기  (0) 2021.04.14
클린코드 4장 - 주석  (0) 2021.04.13
클린코드 3장 - 함수  (0) 2021.04.12
클린코드 1장 - 깨끗한 코드  (0) 2021.04.05
클린코드 정리를 시작하며..  (0) 2021.04.05