Chapter 9

데이터 조직화

  • 9.1 변수 쪼개기
  • 9.2 필드 이름 바꾸기
  • 9.3 파생 변수를 질의 함수로 바꾸기
  • 9.4 참조를 값으로 바꾸기
  • 9.5 값을 참조로 바꾸기
  • 9.6 매직 리터럴 바꾸기

데이터를 어떻게 표현하느냐가 코드의 명확성을 결정해. 9장은 변수와 필드를 더 잘 조직하는 기법들을 모아놓았어. 변수 쪼개기, 참조와 값의 차이, 의미 없는 숫자에 이름 붙이기까지.

**변수 쪼개기(Split Variable)**는 하나의 변수가 두 가지 이상의 역할을 하고 있으면 쪼개는 거야. 루프 변수나 수집 변수를 제외하면, 변수 하나는 역할 하나만 담당해야 해. 근데 코드를 보면 같은 변수에 값을 여러 번 대입하면서 다른 용도로 쓰는 경우가 있거든.

// Before
let temp = 2 * (height + width);  // 둘레
console.log(temp);
temp = height * width;  // 넓이
console.log(temp);

// After
const perimeter = 2 * (height + width);
console.log(perimeter);
const area = height * width;
console.log(area);

변수를 쪼개면 각각의 역할이 명확해지고, const로 선언할 수도 있어. "대입이 두 번 이상 이뤄진다면 여러 가지 역할을 수행한다는 신호다."

**필드 이름 바꾸기(Rename Field)**는 레코드나 클래스의 필드 이름을 더 명확한 이름으로 바꾸는 거야. 데이터 구조는 프로그램을 이해하는 열쇠인데, 필드 이름이 이상하면 코드를 읽을 때마다 머릿속에서 번역을 해야 해. 레코드가 널리 쓰이고 있으면 한 번에 바꾸기 어렵지. 이때는 캡슐화를 먼저 적용해서, 내부 필드 이름을 바꿔도 외부가 영향받지 않게 만들어.

**파생 변수를 질의 함수로 바꾸기(Replace Derived Variable with Query)**는 다른 데이터에서 계산할 수 있는 변수를 제거하고, 필요할 때마다 계산하게 바꾸는 거야. "가변 데이터는 소프트웨어의 큰 골칫거리다." 파생 변수가 있으면 원본이 바뀔 때 파생 변수도 같이 바꿔야 하는데, 이 동기화를 깜빡하면 버그가 생겨.

// Before
get discountedTotal() { return this._discountedTotal; }
set discount(aNumber) {
  const old = this._discount;
  this._discount = aNumber;
  this._discountedTotal += old - aNumber;  // 파생 변수 동기화
}

// After
get discountedTotal() { return this._baseTotal - this._discount; }
set discount(aNumber) { this._discount = aNumber; }

매번 계산하면 동기화 걱정이 없어. 변환 함수에서 계산하는 파생 데이터는 예외야 — 변환 함수의 목적 자체가 데이터를 한 곳에서 계산하는 거니까.

**참조를 값으로 바꾸기(Change Reference to Value)**는 객체를 참조로 다루는 대신 값 객체로 다루는 거야. 참조 객체는 같은 데이터를 여러 곳에서 공유하고, 한 곳에서 변경하면 다른 곳에서도 바뀌어. 값 객체는 각자 자기만의 복사본을 가지고, 불변이야. 값 객체의 장점은 **"불변이라서 다루기 쉽다"**는 거야. 분산 시스템이나 동시성 환경에서 특히 유리하지. 반대로 **값을 참조로 바꾸기(Change Value to Reference)**는 같은 데이터를 여러 곳에서 공유해야 할 때 적용해. 주문 여러 개가 같은 고객을 참조해야 하고, 고객 정보가 바뀌면 모든 주문에 반영되어야 한다면 참조를 써야 해. 보통 저장소(repository) 패턴으로 구현하지.

**매직 리터럴 바꾸기(Replace Magic Literal)**는 코드에 직접 들어 있는 의미 불명의 숫자나 문자열을 상수로 바꾸는 거야.

// Before
if (aValue > 9.80665) { ... }

// After
const STANDARD_GRAVITY = 9.80665;
if (aValue > STANDARD_GRAVITY) { ... }

뭘 의미하는지 모르겠고, 같은 값이 여러 곳에 흩어져 있으면 바꿀 때 다 찾아야 하니까. 다만 모든 리터럴을 상수로 바꿀 필요는 없어. 0이나 1 같은 건 맥락에서 명확하면 그냥 써도 돼. 그리고 aValue.equals("M")보다 aValue.isMale()이 나아 — 문자열을 상수로 뽑는 것보다 함수를 만드는 게 더 좋은 리팩터링일 때도 있어.


정리

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

  1. "변수 하나에 역할 하나." 대입이 두 번 이상이면 쪼개
  2. "참조와 값은 트레이드오프다." 공유가 필요하면 참조, 불변이 중요하면 값
  3. "매직 리터럴에 이름을 붙여라." 코드가 자기 자신을 설명하게 만들어