해당 내용은 위의 책의 내용을 제가 이해한대로 정리해둔 내용입니다.
이번 포스팅에선 Chapter09의 내용에 대해 다룹니다.
9장에서 소개하는 주요 리팩터링 기법들을 요약하고 예제와 함께 정리했습니다.
Chapter09 데이터 조직화
1.변수 쪼개기 |
2.필드 이름 바꾸기 |
3.파생 변수를 질의 함수로 바꾸기 |
4.참조를 값으로 바꾸기 |
5.값을 참조로 바꾸기 |
6.매직 리터럴 바꾸기 |
데이터 조직화의 효과
- 명확성 향상: 데이터 구조와 변수 사용이 명확해져 코드 이해도 증가
- 가변성 제어: 변수의 값이 변경되는 지점을 최소화하여 예측 가능성 높임
- 오류 감소: 데이터 의존성과 변경 지점이 명확해져 오류 발생 가능성 감소
- 계산 투명성: 데이터가 어떻게 도출되는지 명확하게 표현하여 유지보수성 향상
- 디버깅 용이성: 데이터 흐름이 명확해져 문제 추적이 용이해짐
데이터 조직화 기법
1. 변수 쪼개기
- 하나의 변수가 여러 용도로 사용될 때 용도별로 분리
- 변수의 책임을 명확히 하고, 코드의 가독성 향상, 의도를 명확히 표현
// Before
function distanceTravelled(scenario, time) {
let result;
let acc = scenario.primaryForce / scenario.mass; // 가속도(a) = 힘(F) / 질량(m)
let primaryTime = Math.min(time, scenario.delay);
result = 0.5 * acc * primaryTime * primaryTime; // 전파된 거리: (1/2)at²
let secondaryTime = time - scenario.delay;
if (secondaryTime > 0) {
// 두 번째 힘을 반영해 가속도 변경
acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass;
result += acc * secondaryTime * secondaryTime / 2;
}
return result;
}
// After
function distanceTravelled(scenario, time) {
let result = 0;
const primaryAcceleration = scenario.primaryForce / scenario.mass;
let primaryTime = Math.min(time, scenario.delay);
result = 0.5 * primaryAcceleration * primaryTime * primaryTime;
let secondaryTime = time - scenario.delay;
if (secondaryTime > 0) {
const secondaryAcceleration =
(scenario.primaryForce + scenario.secondaryForce) / scenario.mass;
result += secondaryAcceleration * secondaryTime * secondaryTime / 2;
}
return result;
}
2. 필드 이름 바꾸기
- 필드의 이름을 더 명확하게 변경하여 의도 표현
- 데이터 구조의 명확성 향상, 가독성 증가, 유지보수성 개선
// Before
class Organization {
constructor(data) {
this._name = data.name;
this._country = data.country;
}
get name() { return this._name; }
set name(aString) { this._name = aString; }
get country() { return this._country; }
set country(aCountryCode) { this._country = aCountryCode; }
}
// After
class Organization {
constructor(data) {
this._title = data.title || data.name;
this._countryCode = data.countryCode || data.country;
}
get title() { return this._title; }
set title(aString) { this._title = aString; }
get countryCode() { return this._countryCode; }
set countryCode(aCountryCode) { this._countryCode = aCountryCode; }
}
const organization = new Organization({
title: "Acme Gooseberries",
countryCode: "GB"
});
3. 파생 변수를 질의 함수로 바꾸기
- 계산 가능한 값을 저장하는 대신 필요할 때 계산하도록 함수로 변경
- 데이터 일관성 보장, 부수효과 감소, 계산 로직 중앙화
// Before
class Order {
constructor(quantity, item) {
this._quantity = quantity;
this._item = item;
this._discountedPrice = Math.max(0, item.price - Math.max(0, item.discountThreshold - quantity) * item.discountRate);
}
get quantity() { return this._quantity; }
get item() { return this._item; }
get discountedPrice() { return this._discountedPrice; }
set quantity(quantity) {
this._quantity = quantity;
// 수량이 변경될 때마다 할인가격 재계산 필요
this._discountedPrice = Math.max(0, this._item.price -
Math.max(0, this._item.discountThreshold - quantity) * this._item.discountRate);
}
}
// After
class Order {
constructor(quantity, item) {
this._quantity = quantity;
this._item = item;
}
get quantity() { return this._quantity; }
get item() { return this._item; }
get discountedPrice() {
return Math.max(0, this._item.price -
Math.max(0, this._item.discountThreshold - this._quantity) * this._item.discountRate);
}
set quantity(quantity) {
this._quantity = quantity;
// 수량이 변경되면 자동으로 discountedPrice 계산이 갱신됨
}
}
4. 참조를 값으로 바꾸기
- 참조 객체를 값 객체로 변경하여 불변성과 동등성 비교 개선
- 참조 추적 복잡성 감소, 값 비교 단순화, 불변성으로 인한 안정성 향상
// Before (참조)
class Product {
constructor(name, price) {
this._name = name;
this._price = price;
}
get name() { return this._name; }
get price() { return this._price; }
set price(newPrice) { this._price = newPrice; }
}
class Order {
constructor(product, quantity) {
this._product = product;
this._quantity = quantity;
}
get product() { return this._product; }
get total() { return this._product.price * this._quantity; }
}
// 제품 가격 변경이 모든 주문에 영향을 미침
const product = new Product("Widget", 10);
const order1 = new Order(product, 1);
const order2 = new Order(product, 5);
product.price = 15; // 모든 주문의 총액에 영향
// After (값)
class Product {
constructor(name, price) {
this._name = name;
this._price = price;
}
get name() { return this._name; }
get price() { return this._price; }
// 값 객체는 불변이므로 setPrice 대신 새 객체 생성 메서드 제공
applyPriceChange(factor) {
return new Product(this._name, this._price * factor);
}
// 값 객체는 동등성 비교 메서드 필요
equals(other) {
return other instanceof Product &&
this._name === other._name &&
this._price === other._price;
}
}
class Order {
constructor(product, quantity) {
this._product = product; // 이제 값 객체
this._quantity = quantity;
}
get product() { return this._product; }
get total() { return this._product.price * this._quantity; }
}
// 값 객체로 변경 후 각 주문은 독립적
const product = new Product("Widget", 10);
const order1 = new Order(product, 1);
const order2 = new Order(product, 5);
// 새 제품 객체 생성, 기존 주문에는 영향 없음
const updatedProduct = product.applyPriceChange(1.5);
5. 값을 참조로 바꾸기
- 동일 실체를 나타내는 여러 값 객체를 하나의 참조 객체로 통합
- 메모리 사용량 감소, 일관성 있는 데이터 관리, 업데이트 용이성
// Before (값)
class Customer {
constructor(id, name) {
this._id = id;
this._name = name;
}
get id() { return this._id; }
get name() { return this._name; }
}
class Order {
constructor(data) {
this._number = data.number;
// 각 주문마다 고객 객체 생성
this._customer = new Customer(data.customerId, data.customerName);
}
get customer() { return this._customer; }
}
// 동일 고객의 주문이라도 서로 다른 고객 객체 사용
const order1 = new Order({number: "123", customerId: "C001", customerName: "Alice"});
const order2 = new Order({number: "456", customerId: "C001", customerName: "Alice"});
// 고객 정보 변경 시 모든 주문 객체를 찾아 업데이트해야 함
// After (참조)
// 고객 저장소
const customerRepository = {
_customers: new Map(),
registerCustomer(id, name) {
if (!this._customers.has(id)) {
this._customers.set(id, new Customer(id, name));
}
return this.findCustomer(id);
},
findCustomer(id) {
return this._customers.get(id);
}
};
class Customer {
constructor(id, name) {
this._id = id;
this._name = name;
}
get id() { return this._id; }
get name() { return this._name; }
set name(name) { this._name = name; }
}
class Order {
constructor(data) {
this._number = data.number;
// 저장소에서 고객 참조 가져오기
this._customer = customerRepository.registerCustomer(
data.customerId, data.customerName);
}
get customer() { return this._customer; }
}
// 이제 모든 주문이 동일 고객 객체 참조
const order1 = new Order({number: "123", customerId: "C001", customerName: "Alice"});
const order2 = new Order({number: "456", customerId: "C001", customerName: "Alice"});
// 고객 정보 변경 시 모든 주문에 자동 반영
order1.customer.name = "Alice Cooper";
console.log(order2.customer.name); // "Alice Cooper"
6. 매직 리터럴 바꾸기
- 코드에 등장하는 의미있는 상수값을 기호 상수로 교체
- 값의 의미 명확화, 재사용성 향상, 값 변경 시 유지보수 용이성
// Before
function potentialEnergy(mass, height) {
return mass * height * 9.81; // 9.81 = 중력 가속도
}
if (temperature > 70) { // 70 = 화씨 70도 (경고 임계값)
alert("위험한 온도입니다!");
}
// After
const GRAVITATIONAL_ACCELERATION = 9.81;
function potentialEnergy(mass, height) {
return mass * height * GRAVITATIONAL_ACCELERATION;
}
const TEMPERATURE_WARNING_THRESHOLD_F = 70;
if (temperature > TEMPERATURE_WARNING_THRESHOLD_F) {
alert("위험한 온도입니다!");
}
마무리
데이터 조직화는 코드의 명확성, 일관성, 유지보수성에 큰 영향을 미칩니다. 9장에서 소개된 리팩터링 기법들은 데이터를 더 명확하고 안전하게 다루는 방법을 제공합니다.
JavaScript에서는 특히 가변 데이터 구조가 일반적이므로, 변수의 역할과 범위를 명확히 하고 값의 변경을 제어하는 것이 중요합니다. 파생 변수를 질의 함수로 바꾸는 기법은 데이터 일관성을 유지하는 데 큰 도움이 됩니다.
참조와 값 객체의 선택은 상황에 따라 다릅니다. 데이터의 공유와 일관성이 중요할 때는 참조를, 불변성과 독립성이 중요할 때는 값 객체를 선택하는 것이 좋습니다. 특히 함수형 프로그래밍에서는 불변 데이터 구조를 선호하므로 값 객체의 사용이 자연스럽습니다.
// 불변 데이터 구조를 활용한 함수형 접근 예시
function updateOrder(order, newQuantity) {
// 기존 객체를 변경하지 않고 새 객체 반환
return {
...order,
quantity: newQuantity,
total: order.price * newQuantity
};
}
// 사용 예
const originalOrder = { id: 1, item: "Book", price: 20, quantity: 1, total: 20 };
const updatedOrder = updateOrder(originalOrder, 2);
// originalOrder는 변경되지 않음
TypeScript를 사용하면 타입 시스템을 통해 데이터 구조의 의도를 더 명확하게 표현할 수 있습니다. 특히 readonly 제어자, 인터페이스, 유니언 타입 등을 활용하면 값 객체와 참조 객체의 특성을 더 잘 표현할 수 있습니다.
// TypeScript에서 값 객체 표현
interface ProductProps {
readonly name: string;
readonly price: number;
}
class Product {
readonly name: string;
readonly price: number;
constructor(props: ProductProps) {
this.name = props.name;
this.price = props.price;
}
// 새 객체 생성 메서드
withPrice(newPrice: number): Product {
return new Product({
name: this.name,
price: newPrice
});
}
}
결론적으로, 데이터 조직화 기법은 코드의 품질을 향상시키는 중요한 요소입니다. 변수의 용도, 이름, 계산 방식, 참조 관계 등을 명확히 함으로써 코드의 가독성과 유지보수성을 크게 개선할 수 있습니다. 언어와 패러다임에 관계없이 이러한 기본 원칙을 적용하면 더 견고하고 이해하기 쉬운 코드를 작성할 수 있습니다.
'개발' 카테고리의 다른 글
[리팩터링 2판] JavaScript 리팩터링 도서학습 #7 (5) | 2025.05.19 |
---|---|
[리팩터링 2판] JavaScript 리팩터링 도서학습 #6 (6) | 2025.05.17 |
[리팩터링 2판] JavaScript 리팩터링 도서학습 #4 (4) | 2025.04.24 |
[리팩터링 2판] JavaScript 리팩터링 도서학습 #3 (5) | 2025.04.17 |
[리팩터링 2판] JavaScript 리팩터링 도서학습 #2 (4) | 2025.03.29 |
댓글