콘텐츠로 이동

리팩터링 실전 강의 교재

9장 — 데이터 조직화

대상: Java/Spring 백엔드 입문~중급 수강생 형식: 개념 → 비유 → Before/After → 절차 → 함정 → 체크리스트 → 퀴즈 전제 환경: Java 17+, Spring Boot 3.x


0. 이 장을 시작하기 전에

0.1 학습 목표

  • 변수·필드의 형태와 의미 를 다듬는 6가지 기법.
  • "한 변수가 여러 의미" 같은 사고 잦은 영역 처방.
  • 참조 ↔ 값 의 트레이드오프.
  • 매직 리터럴 을 의미 있는 상수·도메인 객체로.

0.2 큰 그림

[ 의미 분리 ]                    [ 표현 형태 ]                  [ 이름·상수 ]
 9.1 변수 쪼개기                  9.4 참조를 값으로              9.2 필드 이름 바꾸기
 9.3 파생 변수를 질의 함수로       9.5 값을 참조로 (반대)         9.6 매직 리터럴 바꾸기

비유 — 9장은 "주방 라벨 다시 붙이기"입니다.

양념통이 한 통에 두 양념이면(변수 쪼개기), 라벨이 틀렸으면(이름), 매번 계산하는 게 안전한지 저장이 안전한지(파생) — 작지만 사고 잦은 영역.

0.3 현업에서 왜 중요한가

  • 변수 쪼개기·매직 리터럴은 가장 자주 PR에서 보이는 사례.
  • 참조↔값은 JPA·DDD 모델링에서 결정적 (값 객체 vs 엔티티).

9.1 변수 쪼개기 (Split Variable)

한 줄 정의

한 변수가 여러 의미 로 재사용되면 의미별로 분리.

// Before — temp가 둘레와 면적 두 의미
double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);

// After
double perimeter = 2 * (height + width);
System.out.println(perimeter);
double area = height * width;
System.out.println(area);

동기

  • 가변 데이터 (악취 3.6) 처방의 한 형태.
  • 디버깅 시 변수 추적이 명확.
  • 함수 추출(6.1) 가능 — 한 의미 = 한 후보.

절차

  1. 첫 번째 사용을 새 이름 변수로 분리.
  2. 두 번째 의미부터는 원본 변수 이름을 유지하거나 다른 새 이름.
  3. 테스트.

9.2 필드 이름 바꾸기 (Rename Field)

한 줄 정의

필드 이름이 의도를 못 드러내면 즉시 바꿔라. 6.7의 필드 버전.

동기

  • 의도 명료.
  • DB 컬럼명·JSON 키와의 매핑은 별도(@Column(name=...)).

Spring/JPA 현업

// Before
@Column(name = "user_nm")
private String name;   // user_nm? name?

// After
@Column(name = "user_nm")
private String userName;   // 코드는 의도, DB는 레거시 그대로

IDE

  • IntelliJ: Shift + F6.

9.3 파생 변수를 질의 함수로 바꾸기 (Replace Derived Variable with Query)

한 줄 정의

다른 데이터로 계산 가능한 값 은 저장하지 말고 매번 계산.

// Before — 캐시처럼 들고 있다 동기화 사고
public class Order {
    private List<OrderItem> items;
    private double total;   // 들고 있음

    public void addItem(OrderItem i) {
        items.add(i);
        total += i.amount();   // 동기화 잊으면 사고
    }
}

// After — 매번 계산
public class Order {
    private List<OrderItem> items;
    public double total() { return items.stream().mapToDouble(OrderItem::amount).sum(); }
    public void addItem(OrderItem i) { items.add(i); }
}

동기

  • 가변 데이터 (3.6) 처방의 정석.
  • 한 진실의 원천(Single Source of Truth) — 동기화 사고 0.

함정

  • 계산이 매우 비싼 경우 캐싱이 필요할 수 있음. 측정 후 결정.
  • 외부 부작용(DB·네트워크) 이 계산에 끼면 함수형이 안 됨.

Effective Java 연결

Item 17(불변)·Item 67(최적화는 측정 후).


9.4 참조를 값으로 바꾸기 (Change Reference to Value)

한 줄 정의

객체를 불변 값 객체 로 바꿔, 동일성(equals)으로 비교.

// Before — 가변, 식별성 비교
public class TelephoneNumber {
    private String areaCode;
    private String number;
    public void setAreaCode(String a) { this.areaCode = a; }
}

// After — 불변 record
public record TelephoneNumber(String areaCode, String number) {
    public TelephoneNumber {
        Objects.requireNonNull(areaCode);
        Objects.requireNonNull(number);
    }
}

동기

  • 같은 값을 안전하게 공유 가능.
  • equals/hashCode 자동 (record).
  • 동시성 안전 자동.

Effective Java 연결

Item 17(불변)·Item 50(방어적 복사 불필요).


9.5 값을 참조로 바꾸기 (Change Value to Reference)

한 줄 정의

같은 개체를 여러 곳에서 가리켜야 하면 참조 객체 로. 9.4의 반대.

동기

  • 한 곳에서 변경하면 모두 보임 (의도된 공유).
  • DDD에서 엔티티(Entity) 가 곧 참조 — ID로 식별, 상태 변경 가능.

9.4 vs 9.5 — DDD 관점

9.4 값 9.5 참조
같은 값이면 같은 객체 (equals) 같은 ID면 같은 객체 (==)
불변 (모든 변경은 새 인스턴스) 가변 (상태 변경 가능)
Money, Period, Email User, Order, Product
DDD Value Object DDD Entity

→ 도메인 모델링의 핵심 결정.


9.6 매직 리터럴 바꾸기 (Replace Magic Literal)

한 줄 정의

코드 안의 숫자/문자열 상수 를 의미 있는 이름의 상수로.

// Before
if (employee.type == 2) ...
double bmi = weight / (height * height);   // height의 단위?

// After
if (employee.type == EmployeeType.MANAGER) ...

private static final int METERS = 1;
double bmi = weight / (height * height);   // 의도가 보임 (또는 enum)

동기

  • 의도가 코드에 보임.
  • 한 곳만 바꾸면 됨.
  • 같은 상수의 의미 일관성.

처방 단계

  1. 이름 있는 상수 (public static final)
  2. enum (관련 상수의 묶음)
  3. 도메인 객체 (Money, Period 등)

Effective Java 연결

Item 22(상수 인터페이스 안티패턴 — 상수는 enum/유틸 클래스로).

함정

  • 모든 숫자를 상수화하는 것도 과함. 의미가 있는 숫자만.
  • 0, 1 같은 boundary는 보통 그대로 두는 게 명료.

9장 종합 정리

한눈에 보는 결정 가이드

상황 선택
한 변수가 여러 의미 변수 쪼개기(9.1)
필드 이름이 의도 못 드러냄 필드 이름 바꾸기(9.2)
계산 가능한 값을 저장 중 파생 변수를 질의 함수로(9.3)
같은 값 객체를 가변으로 들고 있음 참조를 값으로(9.4)
같은 개체를 여러 곳에서 식별·공유 값을 참조로(9.5)
의미 있는 숫자·문자열 리터럴 매직 리터럴 바꾸기(9.6)

종합 체크리스트

  • 한 변수에 두 의미가 섞여 있지 않은가 (9.1)
  • 캐시처럼 저장한 파생값이 동기화 사고 위험인가 (9.3)
  • 값 객체(Money 등)가 가변·setter 노출되어 있는가 (9.4)
  • 같은 상수 숫자·문자열이 여러 곳에 흩어짐 (9.6)

종합 퀴즈

Q1. 파생 변수를 질의 함수(9.3)로 바꾸는 가장 큰 이점?

A. Single Source of Truth. 저장된 파생값은 원본과 동기화 잊는 순간 깨진다. 매번 계산하면 진실의 원천이 하나 → 깨질 일 없음. 동시성 안전 자동 보너스.

Q2. 9.4와 9.5가 둘 다 있는 이유? — DDD 용어로?

A. 값 객체(Value Object)엔티티(Entity) 의 차이. 같은 값이면 같은 것(Money 100원)은 값, 같은 ID면 같은 것(User#42)은 엔티티. 도메인 모델링에서 이 결정이 객체 생애주기·동시성 정책을 결정.

Q3. 매직 리터럴을 enum으로 바꾸는 게 상수보다 좋은 이유는?

A. 타입 안전성 — int type = 2 는 잘못된 값 차단 못 하지만, EmployeeType.MANAGER 는 정의된 값만 가능. Effective Java Item 34와 같은 결.

Q4. 변수 쪼개기(9.1)와 가변 데이터(악취 3.6)의 관계?

A. 한 변수에 여러 값을 차례로 대입하는 게 가변 데이터의 가장 흔한 형태. 의미별로 쪼개면 각 변수가 사실상 final 이 되어 가변성 자체가 사라짐. 9.1은 가변→불변 전환의 첫 단계.


다음 장 예고 — 10장: 조건부 로직 간소화

if/switch/null 처리를 정리하는 7가지 — 분해·통합·보호 구문·조건부 로직을 다형성으로 ★·특이 케이스·어서션. 책의 중요 ★ 중 하나(10.4)가 여기.