관리 메뉴

김종권의 iOS 앱 개발 알아가기

[Refactoring] 5-4. 기본적인 리펙토링 (단계 쪼개기) 본문

Refactoring (리펙토링)

[Refactoring] 5-4. 기본적인 리펙토링 (단계 쪼개기)

jake-kim 2023. 3. 20. 01:25

리펙토링 핵심

  • 각 방법들을 '왜' 수행해야 하는지 깨닫고 유연하게 적용하기

단계 쪼개기

  • 서로 다른 두 대상을 한꺼번에 다루는 코드를 발견하면 각각을 별개 기능으로 나누는 방법
    • 코드를 수정할 때 두 대상을 동시에 생각할 필요 없이 하나에만 집중할 수 있게끔하기 위함
    • 기능이 잘 분리되어 있다면 다른 기능의 상세 내용은 전혀 기억하지 못해도 원하는 대로 수정을 쉽게 접근이 가능

단계 쪼개기

  • 단계 쪼개기 전략
    • 1) 입력을 단순한 형태로 변경 (입력이 처리 로직에 적합하지 않은 형태로 들어오는 경우, 먼저 입력값을 다루기 편한 형태로 가공할것)
    • 2) 처리 로직을 순차적인 단계들로 분리하고 이 단계는 서로 확연히 다른 일을 수행하도록 구현

ex) 단계 쪼개기 전략이 적용된 사례 - 컴파일러

  • 컴파일러는 어떤 코드를 입력받아서, 실행 가능한 형태로 변환하는데, 컴파일러 작업은 여러 단계가 순차적으로 연결된 형태로 분리되어 있음
    • 단계 쪼개기: 코드(텍스트)를 토큰화 -> 토큰 파싱 -> 구문 트리 생성 -> object code 생성
    • 장점: 각 단계는 자신만의 문제에 집중하기 때문에 나머지 단계에 관해서는 자세히 몰라도 이해하기가 가능

ex) 상품의 결제 금액과 배송비를 구하는 코드

  • 관련 모델 (0, 1, 2, 3으로 할당된 값들은 편한 )
struct Product {
    let basePrice: Double
    let discountRate: Double
    let discountThreshold: Double
}

struct ShippingMethod {
    let discountThreshold: Double
    let discountRate: Double
    let discountFee: Double
    let feePerCase: Double
}
  • 계산하는 코드
    • 문제점: 계산이 두 단계로 구성 (상품 가격, 배송비)
    • 추후에 상품 가격과 배송비 계산을 더 복잡하게 만드는 변경이 생기면 더욱 복잡해질 수 있으므로 코드를 두 단계로 나눌 것
func priceOrder(product: Product, quantity: Double, shippingMethod: ShippingMethod) -> Double {
    let basePrice = product.basePrice * quantity
    let discount = max(quantity - product.discountThreshold, 0) * product.basePrice * product.discountRate
    let shippingPerCase = (basePrice > shippingMethod.discountThreshold) ? shippingMethod.discountFee : shippingMethod.feePerCase
    let shippingCost = quantity * shippingPerCase
    let price = basePrice - discount + shippingCost
    return price
}
  • 리펙토링 - 별도의 함수를 하나 만들어서 단계 나누기
    • 새로 생긴 문제점: 매개변수가 너무 많아서 읽기가 불편한점
func priceOrder(product: Product, quantity: Double, shippingMethod: ShippingMethod) -> Double {
    let basePrice = product.basePrice * quantity
    let discount = max(quantity - product.discountThreshold, 0) * product.basePrice * product.discountRate
    let price = applyShipping(basePrice: basePrice, shippingMethod: shippingMethod, quantity: quantity, discount: discount)
    return price
}

// 배송비 적용 코드
func applyShipping(basePrice: Double, shippingMethod: ShippingMethod, quantity: Double, discount: Double) -> Double {
    let shippingPerCase = (basePrice > shippingMethod.discountThreshold) ? shippingMethod.discountFee : shippingMethod.feePerCase
    let shippingCost = quantity * shippingPerCase
    let price = basePrice - discount + shippingCost
    return price
}
  • 리펙토링 - PriceData라는것을 하나 만들어서 주고받을 중간 데이터 구조를 만들것
    • 장점: 매개변수를 PriceData를 만들어서 price에 관한 정보를 응집화하도록 구현
struct PriceData {
    let basePrice: Double
    let quantity: Double
    let discount: Double
}

func priceOrder(product: Product, quantity: Double, shippingMethod: ShippingMethod) -> Double {
    let basePrice = product.basePrice * quantity
    let discount = max(quantity - product.discountThreshold, 0) * product.basePrice * product.discountRate
    let priceData = PriceData(basePrice: basePrice, quantity: quantity, discount: discount)
    let price = applyShipping(priceData: priceData, shippingMethod: shippingMethod)
    return price
}

func applyShipping(priceData: PriceData, shippingMethod: ShippingMethod) -> Double {
    let shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold) ? shippingMethod.discountFee : shippingMethod.feePerCase
    let shippingCost = priceData.quantity * shippingPerCase
    let price = priceData.basePrice - priceData.discount + shippingCost
    return price
}
  • 현재 priceOrder 함수를 보면 아직 단계가 완벽히 나누어진게 아니므로 리펙토링이 더 필요
    • price와 연관된 코드들을 별도 함수로 빼면 더욱 상품 가격배송비에 관한 코드 분리가 가능
func priceOrder(product: Product, quantity: Double, shippingMethod: ShippingMethod) -> Double {
    // 첫 번째 단계
    let priceData = calculatePricingData(product: product, quantity: quantity)
    
    // 두 번째 단계
    let price = applyShipping(priceData: priceData, shippingMethod: shippingMethod)
    
    return price
}

// 첫 번째 단계를 처리하는 함수
func calculatePricingData(product: Product, quantity: Double) -> PriceData {
    let basePrice = product.basePrice * quantity
    let discount = max(quantity - product.discountThreshold, 0) * product.basePrice * product.discountRate
    let priceData = PriceData(basePrice: basePrice, quantity: quantity, discount: discount)
    return priceData
}

// 두 번째 단계를 처리하는 함수
func applyShipping(priceData: PriceData, shippingMethod: ShippingMethod) -> Double {
    let shippingPerCase = (priceData.basePrice > shippingMethod.discountThreshold) ? shippingMethod.discountFee : shippingMethod.feePerCase
    let shippingCost = priceData.quantity * shippingPerCase
    let price = priceData.basePrice - priceData.discount + shippingCost
    return price
}

단계 쪼개기 정리

  • 코드를 수정할 때 두 대상을 동시에 생각할 필요 없이 하나에만 집중할 수 있게끔하기위해 단계 쪼개기
  • 로직을 단순히 쪼개기 전에, 입력받는것을 처리하기 쉬운 형태로 변경이 필요 (위에서 알아본 PriceData)

* 전체 코드: https://github.com/JK0369/ExRefactoring_5-4

* 참고

- Refactoring (Marting Flowler)

 

Comments