Book/Clean Code

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

의미 있는 이름

의도를 명확히 밝혀라

  • 변수, 함수, 클래스 이름은 변수의 존재 이유, 수행 기능, 사용 방법 같은 내용을 모두 담고 있어야 한다.
  • 코드가 하는 일을 짐작하기 어려워선 안되며, 코드의 맥락이 코드 자체에 명시적으로 드러나야 한다.
  • 지뢰찾기 게임을 만든다고 가정했을 때, 해당 코드들을 비교해보자.
    • 단순히 이름만 고쳤을 뿐인데 함수가 하는 일을 이해하기 쉬워졌다.
public List<int[]> getThem(){
	List<int[]> list1= new ArrayList<>();
	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;
}

 

그릇된 정보를 피하라

  • 잘못된 정보는 코드의 의미를 흐린다.
  • 널리 쓰이는 의미가 있는 단어를 다른 의미로 사용해도 안된다.
    • 계정을 담는 컨테이너가 List가 아님에도 변수 이름을 accountList라고 명명해선 안된다.
  • 모듈 둘이 흡사한 이름을 사용하지 않도록 주의하자.

 

의미 있게 구분하라

  • 읽는 사람이 차이를 알도록 이름을 지어야 한다.
  • 코드를 읽다가 다음과 같은 예에 마주하게 되면 읽는 사람은 그 차이를 알 수가 없다.
    • money vs moneyAmount, customerInfo vs customer, accountData vs account
  • 의미없는 단어(불용어)는 그저 중복일 뿐이다.
    • NameString이라고 의미없는 String을 붙인 이름이 Name 보다 나은 점이 없다.
  • 연속적인 숫자를 덧붙인 이름은 의도적인 이름과 정반대이다.
    • public void copyChars(char a1[],char a2[])
    • 위 코드에선 변수 a1, a2가 무엇을 의미하는지 알 수 없다. 차라리 source, destination이라고 짓는 게 훨씬 의미를 알아채기 쉽다.

 

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

  • 긴 이름이 짧은 이름보다, 검색하기 쉬운 이름이 상수보다 좋다.
    • 변수 이름을 s라고 짓는 것 보다, sum이라고 짓는 게 훨씬 검색이 쉽다.
  • 상수를 사용할 땐 따로 변수를 정의해서 사용한다.
    • 숫자 5를 찾는 것 보다, WORK_DAYS=5로 정의하고, 해당 변수를 사용하는 게 검색에 용이하다.

 

인코딩을 피하라

  • 변수에 부가 정보를 더하지 마라.
  • 헝가리안 표기법
    • 변수에 굳이 타입을 명시하지 않도록 한다. 간혹 변수의 타입이 바뀌어도, 변수 명이 바뀌지 않아 혼란을 줄 수 있다.
  • 멤버 변수 접두어
    • 멤버 변수에 굳이 m_이라는 접두어를 붙이지 말라.
  • 인터페이스와 구현 클래스
    • 도형을 생성하는 Factory를 구현한다고 가정했을 때, 인터페이스 명은 IShapeFactory, 구현체는 ShapeFactory라고 짓는 것 보다 인터페이스는 내버려두고, 구현체를 인코딩하여 ShapeFactoryImpl이라고 짓는 게 낫다.
    • 인터페이스 이름은 접두어를 붙이지 않는 편이 좋다고 생각함. 내가 다루는 클래스가 인터페이스라는 사실을 알리고 싶지 않다.

 

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

  • 독자가 코드를 읽으면서 변수 이름을 자신이 아는 이름으로 변환해야 한다면, 이는 바람직하지 않다.
  • 루프에서 반복 횟수를 세는 변수 i,j..를 제외하고 변수 이름을 한 글자로 짓는 것은 지양하자.
  • r이라는 변수를 매번 url로 기억하는 사람은 똑똑한 프로그래머이겠지만, 똑똑한 프로그래머와 달리 전문가 프로그래머는 언제나 명료함이 최고라는 사실을 이해한다.

 

클래스, 메소드 명명법

  • 클래스, 객체 이름은 명사가 적합하다
  • 메소드 이름은 동사가 적합하다. 접근자, 변경자, 조건자 등은 표준에 따라 get,set,is를 붙인다.
Strin name = employee.getName()
customer.setName("mike")
if(payCheck.isPosted())
  • 생성자를 오버로드할 경우 정적 팩토리 메서드를 사용한다.
Complex fulcrumPoint=new Complex(23.0);
Complex fulcrumPoint = Complex.FromRealNumber(23.0); //아래가 더 좋다.

 

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

  • 추상적인 개념하나에 단어 하나를 선택해 이를 고수한다.
  • IDE에서는 문맥에 맞는 단서를 제공하는데, 예를 들어 객체를 사용하면 그 객체가 제공하는 메소드 목록을 보여준다. 이때, 그 코드까지 가지 않고도 메소드를 이해할 수 있도록 하는 게 좋다.
  • 동일 코드 기반에 Controller와 Manager, Driver를 혼용하면 각 역할이 같음에도 당연히 독자는 클래스도, 타입도 다를 거라고 생각한다.

말장난을 하지 마라

  • 한 단어를 두 가지 목적으로 사용하지 마라.
  • 예를 들어, 여러 클래스에 add라는 메소드가 생겼을 때, 모든 메소드의 매개변수와 변환값이 의미적으로 똑같다면 문제가 없다.
  • 그러나 같은 맥락이 아니라면 insert, append등의 해당 맥락을 표현할 다른 이름을 찾아보자.

 

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

  • 해법 영역
    • 코드를 읽을 사람도 프로그래머이니, 문제를 해결하기 위해 사용하는 전산 용어, 알고리즘, 패턴, 수학 용어 등을 사용해도 괜찮다. 모든 이름을 문제 영역에서 가져오는 정책은 현명하지 못하다.
    • Visitor 패턴에 익숙한 프로그래머는 AccountVisitor라는 이름을 금방 이해한다. 기술 개념에는 기술 이름이 가장 적합하다.
  • 문제 영역
    • 적절한 프로그래밍 용어가 없다면, 문제 영역에서 이름을 가져온다.
    • 코드를 보수하는 프로그래머가 해당 분야 전문가에게 의미를 물어 파악할 수 있다.

 

의미 있는 맥락을 추가하라

  • 스스로 의미가 분명하지 않은 이름이 있다면, 클래스, 함수, namespace에 추가하여 맥락을 부여하고, 나머지 방법이 실패하면 접두어를 붙인다.
  • street,houseNumber, city, state, zipcode라는 변수 이름들은 각각이 같이 있다면 파악하기 쉬우나, state라는 변수를 단독으로 사용한다면 의미를 알아채기 쉽지 않다. 따라서 접두어로 address를 의미하는 addr를 붙여주면 addrState로, 주소를 나타내는 주라는 것을 인식할 수 있다.
  • 물론 Address라는 클래스를 생성하고, 각 변수들을 안에 넣어 맥락을 부여하는 것이 더 좋다.
  • Example
    • Bad Case
      • 우선 함수가 길며, 세 변수를 함수 전반에서 사용한다.
// 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);
}
  • Good Case
    • 세 변수를 클래스에 넣어 맥락을 분명하게 하였다.
    • 맥락을 개선하면 함수를 쪼개기가 쉬워지고, 알고리즘이 명확해진다.
// 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";
    }
}

 

마치면서

  • 좋은 이름을 선택하려면 설명 능력이 뛰어나야 하며, 문화적 배경이 같아야 한다.
  • 암기는 요즘 나오는 도구에게 맡기고, 우리는 문장과 문단처럼, 표나 자료구조처럼 읽히는 코드를 작성하려고 노력하자.

 

 

+) 2장을 읽은 소감

점점 코드를 깨끗하게 짜려는 노력이 필요한게 느껴져서 클린코드를 읽어보기 시작했다. 내가 무의식적으로 사용했던 게 bad case로 들어가있을 때는 찔리기도 했고, 크게 공감되는 부분도 있었고, 아직은 이해가 잘 안되는 부분도 있었다. 책을 쭉 읽어보면서 좋은 부분을 내 것으로 만들어보려는 노력을 쉬지 말자!

또한 이름을 짓는데 한 장을 할당할 필요가 있나도 싶었지만. 결국은 코드는 변수명, 메소드명, 클래스명등의 이름들을 조합하는 것이니, 소설처럼 술술 읽히는 코드를 짜기 위한 출발점은 좋은 이름 짓기에서 출발한다는 느낌이 들었다. 앞으로 코드를 짜면서 생각날 때마다 위의 규칙들을 적용해봐야겠다.