리팩터링 실전 강의 교재¶
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) 가능 — 한 의미 = 한 후보.
절차¶
- 첫 번째 사용을 새 이름 변수로 분리.
- 두 번째 의미부터는 원본 변수 이름을 유지하거나 다른 새 이름.
- 테스트.
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)
동기¶
- 의도가 코드에 보임.
- 한 곳만 바꾸면 됨.
- 같은 상수의 의미 일관성.
처방 단계¶
- 이름 있는 상수 (
public static final) - enum (관련 상수의 묶음)
- 도메인 객체 (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)가 여기.