Chapter 1

코드 정리 기법 I

  • 1.1 보호 구문
  • 1.2 안 쓰는 코드
  • 1.3 대칭 맞추기
  • 1.4 새 인터페이스로 기존 루틴 부르기
  • 1.5 읽는 순서
  • 1.6 응집도 배치
  • 1.7 선언과 초기화
  • 1.8 설명하는 변수

코드 정리의 출발점은 놀라울 만큼 사소하지. 거창한 리팩터링이 아니라, 조건문 하나 평탄하게 만들고, 안 쓰는 코드 지우고, 변수 이름 하나 바꾸는 것부터 시작하는 거야.

보호 구문(Guard Clauses) 부터 보자. 중첩된 if문 안에 진짜 로직이 파묻혀 있는 코드, 다들 본 적 있잖아.

if (condition1) {
    if (condition2) {
        if (condition3) {
            // 드디어 진짜 로직
        }
    }
}

이걸 보호 구문으로 뒤집으면 이렇게 되지:

if (!condition1) return
if (!condition2) return
if (!condition3) return
// 진짜 로직

예외 상황을 먼저 걸러내고, 핵심 로직은 들여쓰기 없이 쭉 읽히게 만드는 거야. "이 조건이 아니면 여기서 끝"이라는 의도가 명확해지고, 머릿속에 쌓이는 인지 부하가 확 줄어들지.

안 쓰는 코드는 더 간단해. 죽은 코드(Dead Code) -- 아무도 호출하지 않는 함수, 절대 참이 될 수 없는 조건 분기, 주석 처리된 코드 블록. 이런 거 보면 "나중에 쓰려고 남겨둔 건가?" 고민하게 되잖아. 그 고민 자체가 비용이야. 안 쓰는 코드는 지워라. 나중에 필요하면 버전 관리 시스템에서 꺼내면 돼. 실제로 다시 꺼낼 일은 거의 없고, 정말 필요하면 그때 새로 짜는 게 낫지.

대칭 맞추기는 같은 종류의 일을 하는 코드들이 서로 다른 스타일로 작성되어 있을 때 쓰는 정리법이야. 어떤 곳에서는 if/else, 비슷한 다른 곳에서는 삼항 연산자, 또 다른 곳에서는 early return. 각각은 문제없지만, 같은 패턴의 코드가 다른 모양이면 "혹시 의도적으로 다르게 한 건가?" 의심하게 되거든. 동일한 패턴에는 동일한 형식을 적용하면 차이가 있는 곳에만 진짜 차이가 있다는 게 명확해져.

새 인터페이스로 기존 루틴 부르기는 호출해야 하는 루틴의 인터페이스가 불편할 때 쓰는 거야. 매개변수 순서가 이상하거나, 이름이 헷갈리거나, 필요 없는 매개변수를 넘겨야 하거나. 그렇다고 기존 루틴을 바꾸자니 다른 곳에서도 쓰고 있잖아. 이럴 때 내가 원하는 인터페이스로 새 함수를 만들고, 그 안에서 기존 루틴을 호출하면 돼.

// 기존 루틴 -- 인터페이스가 불편함
oldRoutine(flag, null, data, callback)

// 내가 원하는 인터페이스
function doSomething(data) {
    return oldRoutine(true, null, data, defaultCallback)
}

기존 코드는 건드리지 않으니 안전하고, 나중에 호출자가 모두 새 인터페이스로 옮겨가면 그때 기존 루틴을 정리할 수도 있지.

읽는 순서도 중요해. 파일 안에서 함수나 변수의 순서가 뒤죽박죽이면 읽기 힘들거든. 위에서 아래로 읽어나가는데 갑자기 아직 정의 안 된 함수가 나오거나, 관련 없는 함수가 끼어 있으면 흐름이 끊기잖아. 읽는 사람이 자연스럽게 위에서 아래로 읽을 수 있도록 순서를 맞춰. 호출하는 함수가 위에, 호출되는 함수가 아래에. 코드를 잘라서 붙여넣기만 하면 되니까 동작에 아무 영향도 없어.

응집도를 높이는 배치는 읽는 순서와 비슷하지만 초점이 달라. 여기서는 "함께 바뀌는 코드를 함께 두라"가 핵심이야. 어떤 기능을 수정할 때 파일 여기저기를 왔다 갔다 해야 한다면, 관련 코드가 흩어져 있다는 신호지. 한 파일 안에서 관련된 함수들을 가까이 배치하거나, 파일 사이에서 관련 코드를 같은 파일로 옮기거나. 함께 바뀌는 코드를 함께 두면 변경할 때 여러 곳을 돌아다니지 않아도 돼. 읽는 순서와 응집도 배치가 충돌하면, 보통 응집도 쪽이 더 중요하지.

선언과 초기화를 함께 옮기기도 아주 간단한 정리법이야. 변수를 파일 위쪽에서 선언하고 한참 아래에서 초기화하는 코드, 예전 C 스타일의 잔재거든.

int x;
// ... 20줄의 관련 없는 코드 ...
x = computeValue();

이걸 int x = computeValue();로 바꾸면, 읽는 사람이 "이 변수가 뭐였더라?" 하고 위로 스크롤할 필요가 없어져. 단, 선언과 초기화 사이에 있는 코드가 그 변수에 의존하지 않는지 확인해야 해.

설명하는 변수는 복잡한 표현식을 이해하기 쉽게 만드는 정리법이야.

// 이게 뭔 뜻이야?
if (order.total > 100 && customer.loyaltyYears > 2 && !order.hasDiscount)

// 아, 그런 뜻이구나
const isEligibleForDiscount = order.total > 100
    && customer.loyaltyYears > 2
    && !order.hasDiscount;
if (isEligibleForDiscount)

표현식에 이름을 붙여서 의도를 드러내는 거지. 컴퓨터는 둘 다 똑같이 실행하지만, 사람이 읽을 때는 완전히 달라. 디버깅할 때도 중간값을 확인할 수 있어서 편하고, 더 큰 리팩터링의 디딤돌이 되기도 해.


정리

1장 읽고 기억할 거 세 가지:

  1. 코드 정리는 사소한 데서 시작하는 거야. 보호 구문으로 조건문 평탄하게, 안 쓰는 코드 삭제, 대칭 맞추기 -- 전부 동작은 안 바뀌고 가독성만 좋아지지
  2. 함께 바뀌는 코드는 함께 두고, 읽히는 순서대로 배치해. 선언과 초기화도 가까이. 이게 응집도를 높이는 기본이야
  3. 이름이 핵심이야. 설명하는 변수로 표현식에 의도를 담고, 불편한 인터페이스는 래퍼 함수로 감싸서 원하는 모양으로 만들어