본문 바로가기
개발

[리팩터링 2판] JavaScript 리팩터링 도서학습 #5

by 개발과 운동, 그리고 책장 2025. 5. 15.

 

해당 내용은 위의 책의 내용을 제가 이해한대로 정리해둔 내용입니다.

이번 포스팅에선 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
    });
  }
}

 

결론적으로, 데이터 조직화 기법은 코드의 품질을 향상시키는 중요한 요소입니다. 변수의 용도, 이름, 계산 방식, 참조 관계 등을 명확히 함으로써 코드의 가독성과 유지보수성을 크게 개선할 수 있습니다. 언어와 패러다임에 관계없이 이러한 기본 원칙을 적용하면 더 견고하고 이해하기 쉬운 코드를 작성할 수 있습니다.

댓글