Chapter 2

코드 정리 기법 II

  • 2.1 설명하는 상수
  • 2.2 명시적 매개변수
  • 2.3 비슷한 코드끼리
  • 2.4 도우미 추출
  • 2.5 하나의 더미
  • 2.6 설명하는 주석
  • 2.7 불필요한 주석 지우기

코드를 읽는 사람이 "이게 뭐지?" 하고 멈추는 순간마다 비용이 발생해. 매직 넘버, 암묵적 의존성, 흩어진 코드 덩어리 -- 전부 그 멈춤을 만드는 주범이지. 여기서 다루는 정리법들은 그 멈춤을 없애는 거야.

설명하는 상수부터 보자. 코드에 숫자나 문자열이 그냥 박혀 있으면 그게 뭘 의미하는지 알 수 없거든.

if (age >= 65)       // 65가 뭐야?
if (status === 3)    // 3이 뭔데?
sleep(86400)         // 이건 또 뭐야?

이걸 상수로 바꾸면:

const RETIREMENT_AGE = 65
const STATUS_APPROVED = 3
const SECONDS_PER_DAY = 86400

리터럴 값에 이름을 붙이면 의도가 드러나고, 나중에 값이 바뀔 때 한 곳만 고치면 돼. 같은 숫자가 여러 곳에 나타나지만 의미가 다른 경우도 있잖아. 60이 어떤 곳에서는 "1분의 초"이고, 다른 곳에서는 "기본 타임아웃"일 수 있어. 상수로 분리하면 이런 구분도 명확해지지.

명시적 매개변수는 함수가 암묵적으로 사용하는 데이터를 드러내는 정리법이야. 함수가 전역 변수나 객체의 깊숙한 속성에 직접 접근해서 데이터를 가져오면, 그 함수가 실제로 뭘 필요로 하는지 알기 어렵거든.

function calculatePrice() {
    const rate = globalConfig.taxRate;     // 어디서 오는 거야?
    const items = shoppingCart.items;       // 이것도?
    // ...
}

이걸 function calculatePrice(taxRate, items)로 바꾸면 함수의 의존성이 시그니처에 드러나. 테스트하기도 쉬워지고 재사용하기도 쉬워지지. 큰 객체를 통째로 넘기는 것보다 실제로 쓰는 값만 꺼내서 넘기는 게 더 좋아.

비슷한 코드끼리 모으는 건 긴 함수를 읽을 때 특히 유용해. 코드 줄들이 뒤섞여 있어서 어디서부터 어디까지가 하나의 "덩어리"인지 모를 때, 관련된 줄들을 모아서 빈 줄로 구분해주면 읽기 쉬워지거든.

const name = user.getName();
const email = user.getEmail();

const order = createOrder(name, email);
const total = calculateTotal(order);

sendConfirmation(email, order, total);

빈 줄 하나가 "여기서부터는 다른 이야기"라는 신호를 주는 거야. 마치 글에서 문단을 나누는 것과 같지. 그리고 이건 나중에 함수로 추출하기 위한 준비 작업이기도 해. 먼저 관련 코드를 모아놓으면 별도 함수로 빼는 게 자연스러워지거든.

도우미 추출이 바로 그 다음 단계야. 코드를 읽다가 "이 부분은 이런 일을 하는구나" 하고 이해한 순간, 그 이해를 함수 이름으로 포착하는 거지.

// before
const taxableAmount = total - deductions;
const tax = taxableAmount * taxRate;
const finalTax = Math.max(0, tax);

// after
const finalTax = calculateTax(total, deductions, taxRate);

코드 덩어리에 이름을 붙이는 것이 도우미 추출의 본질이야. 그 이름이 구현 세부사항을 숨기고 의도를 드러내지. 좋은 이름이 떠오르지 않으면, 아직 추출할 때가 아닌 걸 수도 있어. 켄트 벡은 도우미 추출을 코드 정리의 핵심 기법으로 보는데, 작은 함수들로 분해하면 각각을 이해하기 쉽고 조합하기도 쉽거든.

그런데 하나의 더미는 좀 의외의 방향이야. 앞에서 코드를 쪼개라고 했는데, 이번엔 "때로는 다시 합쳐라"고 말하거든. 코드가 너무 잘게 쪼개져 있으면 오히려 이해하기 어려워. 작은 함수 10개를 왔다 갔다 하면서 읽어야 하고, 전체 흐름이 안 보이잖아. 이럴 때는 먼저 하나의 더미로 합쳐서 전체를 한눈에 본 다음, 거기서 새로운 구조를 찾는 게 나아. 인라인(inline) 리팩터링이라고 생각하면 돼. 합치는 건 끝이 아니라 더 나은 구조를 발견하기 위한 과정이야.

마지막으로 주석 이야기. 설명하는 주석은 코드만으로 표현하기 어려운 것들을 위한 거야. 왜 이런 결정을 했는지, 어떤 배경이 있었는지.

// 고객사 X의 레거시 시스템이 UTF-8을 지원하지 않아서
// ASCII로 변환해야 함 (마이그레이션 후 제거 가능)
const encoded = toAscii(data);

코드가 '무엇'과 '어떻게'를 말해준다면, 주석은 '왜'를 말해줘야 해. 코드를 정리하다가 이해하기 어려운 부분을 발견했는데 당장 구조를 바꾸기는 어려울 때, 내가 알아낸 것을 다음 사람을 위해 기록해두는 거지.

반대로 불필요한 주석은 지워야 해. i++; // i를 1 증가시킨다 같은 건 소음이야. 코드가 이미 말하고 있는 걸 또 말하면 의미 없고, 코드가 바뀔 때 주석을 같이 안 고치면 거짓말이 되거든. 거짓말하는 주석은 주석이 없는 것보다 나빠. 정리하면 간단해: '왜'를 설명하는 주석은 남기고, '무엇'을 반복하는 주석은 지워.


정리

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

  1. 매직 넘버는 상수로, 암묵적 의존성은 매개변수로 드러내. 코드가 스스로 설명하게 만드는 거야
  2. 비슷한 코드끼리 모으고, 이름 붙여서 도우미로 추출해. 단, 너무 잘게 쪼갰으면 다시 합쳐서 전체를 보는 것도 방법이야
  3. 주석은 '왜'만 남기고 '무엇'은 지워. 코드가 바뀌면서 안 고친 주석은 거짓말이 돼