책 'Clean Code' 리뷰 (1)

@inup· January 24, 2025 · 9 min read

1장 - 깨끗한 코드

르블랑의 법칙 (Leblanc's Law)

  • 나중은 결코 오지 않는다.
  • 급해서, 서두르느라... 한 번 작성한 쓰레기 코드를 나중에 정리하는 일은 결코 없다.
  • 쓰레기 코드는 팀의 생산성을 크게 낮춘다. (얽히고설킨 코드는 매번 '해독'을 요구한다) 떨어진 생산성을 높여야 한다는 압박이 결국 더 나쁜 코드를 많이 생산하게 한다.
  • 코드 실패의 근본적 원인은 시시각각 요구사항을 바꿔치는 고객도, 멍청한 관리자도 아닌 우리 프로그래머에게 있다.

깨끗한 코드라는 예술

  • 대부분의 사람들은 잘 그려진 그림과 엉망으로 그려진 그림을 구분할 줄 안다.
  • 그러나, 잘 그려진 그림을 구분하는 능력이 그림을 잘 그리는 능력은 아니다.
  • 깨끗한 코드를 작성하려면 '청결'이라는 힘겹게 습득한 코드 감각을 활용해 자잘한 기법들을 적용하는 절제와 규율이 필요하다.
  • 미적 감각과 마찬가지로, '코드 감각'은 때로는 타고 난다. 누군가는 투쟁해서 얻어야만 한다.
  • '코드 감각'이 없는 사람도 때로는 나쁜 모듈을 알아본다. '코드 감각'이 있는 사람은 나쁜 모듈을 보면 좋은 모듈로의 개선 방안을 떠올릴 수 있다.

보이스카우트 규칙

  • 입력했던 모든 키를 저장하는 '편집 세션'을 다시 재생해보면, 프로그래밍 행위 대부분의 시간은 '스크롤하거나', '다른 코드를 찾아보는' 동작이었다.
  • 주변 코드가 읽기 쉬우면, 새 코드도 짜기 쉽다. 주변 코드를 읽기가 어렵다면, 새 코드도 짜기 어렵다. 그러므로 급하다면, 서둘러 끝내려면, 쉽게 짜려면, 읽기 쉽게 하라.
  • 그리고 언제나 유지하라. ('캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라.')

2장 - 의미 있는 이름

의도를 분명히 밝혀라

  • 좋은 이름으로 지으려면 시간이 많이 걸리지만, 곧 절약하는 시간이 훨씬 더 많다. 그러므로 이름을 주의깊게 살펴 더 나은 이름이 떠오르면 개선하라.
  • 이름은 다음과 같은 굵직한 질문에 모두 답할 수 있어야 한다. 주석이 필요하다면 의도를 분명히 드러내지 못했다는 뜻이다.

    • 변수(또는 함수, 클래스)의 존재 이유는?
    • 수행 기능은?
    • 사용 방법은?

예시 1

// Bad
int d; // What does 'd' stands for?

// Good
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;

예시 2

// Bad
public List<int[]> getThem() {
    List<int[]> list1 = new ArrayList<int[]>();
    for (int[] x : theList) {  // 'theList'에 무엇이 들어있는가?
        if (x[0] == 4) {  // 정수 4는 무슨 의미인가?
            list1.add(x);
        }
    }
    return list1;
}

// Good
public List<int[]> getFlaggedCells() {
    List<int[]> flaggedCells = new ArrayList<int[]>();
    for (int[] cell : gameBoard) {
        if (cell[STATUS_VALUE] == FLAGGED) {
            flaggedCells.add(cell);
        }
    }
    return flaggedCells;
}

그릇된 정보를 피하라

  • dist 는 distance인가? distribution인가? hp는 체력(health points)인가? 빗변(hypotenuse)인가? 코드에 그릇된 단서를 남겨서는 안된다.
  • 실제 List가 아니라면 List라는 이름을 넣지 말아야 한다.
  • 서로 비슷한, 혼동되는 변수명을 채택하지 않는다. (문자 l 과 숫자1 등..)

의미있게 구분하라

  • 숫자를 붙인 이름을 사용하지 않는다. (a1, a2 등..)
  • 불용어(이유없는 단어)를 사용하지 않는다. (Customer를 CustomerObject로 명명하는 것)

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

// Bad
private Date ymdhms; // Year, Month, Day, Hour, Minute, Second

// Good
private Date timestamp;

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

  • 한 글자 단어와 숫자는 검색으로 찾기 어렵다.
  • 7 -> MAX_CLASS_PER_STUDENT 와 같이 검색하기 쉬운 이름을 사용해야 한다.

인코딩을 피하라

  • 과거 컴파일러가 타입을 점검하지 않았던 시기, 모든 변수에 타입을 기억할 단서를 달았다. (헝가리안 표기법. 전화번호를 phoneString과 같이 저장해두었다.)
  • 그러나 요즘 IDE는 코드를 컴파일하지 않고도 타입 오류를 감지한다. 접두어/접미어는 해독을 늦출 뿐이다. 이렇게 하면 타입을 바꾸기에도 곤란해진다.
  • 예를 들어, 클래스 Member의 변수임을 나타내기 위한 m_phone, m_age 등도 마찬가지로 피해야 하는 경우이다.

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

  • 반복문에서 반복 횟수를 저장하는 i, j, k는 괜찮다. 그 외의 문자 하나만 사용하는 변수 이름은 분명 문제가 있다.
  • 명료함(clarity)이 최고라는 사실을 이해해야 한다.

클래스 이름과 메서드 이름

  • 클래스 이름 : 명사 또는 명사구 (Customer, Account 등)
  • 메서드 이름 : 동사 또는 동사구 (postPayment, deletePage 등)

    • 자바 표준에 따라 접근자, 변경자, 조건자는 각각 get, set, is를 붙인다.
    • 생성자를 오버로드할 경우 static 메서드를 사용한다.

      // Bad
      Complex fulcrumPoint = new Complex(23.0); 
      // Good
      Complex fulcrumPoint = Complex.FromRealNumber(23.0);

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

  • '가져오기'라는 의미의 용어를 제각각 fetch, retrieve, get으로 부르면 혼란스럽다.
  • Controller와 Driver, Manager의 차이는? 같은 역할을 한다면, 같은 이름으로 불러야 한다. 타인은 당연히 클래스도 다르고 타입도 다르리라 생각할 것이다.

말장난을 하지 마라

종종 프로그래머는 '일관성'의 늪에 빠져 부적절한 경우에 같은 단어를 사용하기도 한다.

  • A 클래스를 비롯한 나머지 클래스에서는 add라는 메서드를 모두 두 개의 인자를 이어 새로운 하나의 값을 반환하는 역할을 하도록 정했다.
  • 프로그래머는 B 클래스에서 새로운 elem을 Collection에 추가하는 메서드를 새롭게 만들었다. 이 때, 역할과 맥락이 전혀 다른 이 메서드에 add라는 단어를 부여한다면 이는 말장난이다.

의미 있는 맥락을 추가하라

  • 모든 단어가 분명한 의미가 있는 것은 아니다.

    • state만을 보고 주소인지 알수는 없다.
    • addr이라는 접두어를 추가하여 addrFirstName, addrState라 쓰면 맥락이 좀 더 분명해진다.
// 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
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";
    }
}
Complex fulcrumPoint = Complex.FromRealNumber(23.0);
@inup
언제나 감사합니다 👨‍💻