관리 메뉴

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

[Refactoring] 1-2. 리펙토링이란?, 리펙토링의 예시 본문

Refactoring (리펙토링)

[Refactoring] 1-2. 리펙토링이란?, 리펙토링의 예시

jake-kim 2022. 1. 3. 00:38

* [Refactoring] 1-1. 리펙토링이란?, 리펙토링의 예시 먼저 참고

Refactoring - 함수 쪼개기

  • getInvoiceInfo 함수 쪼개기
  • 전체 동작을 각각의 부분으로 나눌 수 있는 지점을 탐색 -> switch 문 확인
func getInvoiceInfo(customer: Customer) -> String {
  var totalAmount = 0 // 토탈 비용
  var volumnCredits = 0 // 포인트 적립
  var result = "청구 내역 (고객명: \(customer.name))\n"
  
  for performance in customer.requestPerformance {
    var thisAmount = 0
    
    switch performance.playId.genre {
    case "comedy": // 희극
      thisAmount = 40000
      if performance.audienceCount > 30 {
        thisAmount += 1000 * (performance.audienceCount - 30)
      }
    case "tragedy": // 비극
      thisAmount = 30000
      if performance.audienceCount > 20 {
        thisAmount += 10000 + 500 * (performance.audienceCount - 30)
      }
    default: fatalError()
    }
    
    // 포인트 적립
    volumnCredits += max(performance.audienceCount - 30, 0)
    
    // 희극 관란객 5명마다 추가 포인트 제공
    if performance.playId.genre == "comedy" {
      volumnCredits += performance.audienceCount / 5
    }
    
    // 청구 내역 출력
    result += "  \(performance.playId): \(thisAmount)원 \(performance.audienceCount)좌석\n"
    totalAmount += thisAmount
  }
  
  result += "총액: \(totalAmount)원\n"
  result += "적립 포인트: \(volumnCredits)점\n\n"
  return result
}​
  • switch문 부분을 amountFor()함수로 따로 쪼개기
private func amountFor(genre: String, audienceCount: Int) -> Int {
  var thisAmount = 0
  switch genre {
  case "comedy": // 희극
    thisAmount = 40000
    if audienceCount > 30 {
      thisAmount += 1000 * (audienceCount - 30)
    }
  case "tragedy": // 비극
    thisAmount = 30000
    if audienceCount > 20 {
      thisAmount += 10000 + 500 * (audienceCount - 30)
    }
  default: fatalError()
  }
  return thisAmount
}

func getInvoiceInfo(customer: Customer) -> String {
  var totalAmount = 0 // 토탈 비용
  var volumnCredits = 0 // 포인트 적립
  var result = "청구 내역 (고객명: \(customer.name))\n"
  
  for performance in customer.requestPerformance {
    var thisAmount = amountFor( // <- 호출
      genre: performance.playId.genre,
      audienceCount: performance.audienceCount
    )

...
  • 이렇게 수정한 후 테스트 코드가 있다면, 실수한 게 없는지 확인
  • 사람은 실수를 하기 때문에 아무리 간단한 수정이라도 테스트할것
  • 오류가 생기더라도 변경 폭이 작기 때문에 살펴볼 범위도 좁아서 문제를 찾고 해결하기가 수월
  • 컴파일 - 테스트 - 커밋

Refactoring - 이름을 명확하게 수정

  • thisAmount를 result로 변경
private func amountFor(genre: String, audienceCount: Int) -> Int {
  var result = 0 // <- 명확한 이름으로 변경
  switch genre {
  case "comedy": // 희극
    result = 40000
    if audienceCount > 30 {
      result += 1000 * (audienceCount - 30)
    }
  case "tragedy": // 비극
    result = 30000
    if audienceCount > 20 {
      result += 10000 + 500 * (audienceCount - 30)
    }
  default: fatalError()
  }
  return result
}
  • 컴파일 - 테스트 - 커밋

Refactoring - 함수 쪼개기 2

  • credit을 계산하는 부분을 함수로 빼기
private func volumnCreditsFor(performance: Content) -> Int {
  // 포인트 적립
  var volumnCredits = max(performance.audienceCount - 30, 0)
  
  // 희극 관란객 5명마다 추가 포인트 제공
  if performance.playId.genre == "comedy" {
    volumnCredits += performance.audienceCount / 5
  }
  return volumnCredits
}
// 적용

func getInvoiceInfo(customer: Customer) -> String {
  var totalAmount = 0 // 토탈 비용
  var volumnCredits = 0 // 포인트 적립
  var result = "청구 내역 (고객명: \(customer.name))\n"
  
  for performance in customer.requestPerformance {
    let thisAmount = amountFor(
      genre: performance.playId.genre,
      audienceCount: performance.audienceCount
    )
    
    volumnCredits += volumnCreditsFor(performance: performance) // <- 호출
    
    // 청구 내역 출력
    result += "  \(performance.playId): \(thisAmount)원 \(performance.audienceCount)좌석\n"
    totalAmount += thisAmount
  }
  
  result += "총액: \(totalAmount)원\n"
  result += "적립 포인트: \(volumnCredits)점\n\n"
  return result
}
  • 컴파일 - 테스트 - 커밋

Refactoring - 반복문 쪼개기

  • 먼저 volumnCredits += 하는 부분은 반복문이 한번 돌때마다 값을 누적하므로 리펙토링이 어렵기 때문에 volumnCredits값이 누적되는 부분으로 따로 뺄 것
for performance in customer.requestPerformance {
  let thisAmount = amountFor(
    genre: performance.playId.genre,
    audienceCount: performance.audienceCount
  )
  
  // 청구 내역 출력
  result += "  \(performance.playId): \(thisAmount)원 \(performance.audienceCount)좌석\n"
  totalAmount += thisAmount
}

for performance in customer.requestPerformance { // <- 값 누적 로직을 별도 for문으로 분리
  volumnCredits += volumnCreditsFor(performance: performance)
}
  • 문장 슬라이드하기(뒤에서 다룰 내용)를 통해서 volumeCredits 변수를 선언하는 문장을 반복문 바로 앞으로 이동
func getInvoiceInfo(customer: Customer) -> String {
  var totalAmount = 0 // 토탈 비용
  var result = "청구 내역 (고객명: \(customer.name))\n"
  
  for performance in customer.requestPerformance {
    let thisAmount = amountFor(
      genre: performance.playId.genre,
      audienceCount: performance.audienceCount
    )
    
    // 청구 내역 출력
    result += "  \(performance.playId): \(thisAmount)원 \(performance.audienceCount)좌석\n"
    totalAmount += thisAmount
  }

  // 문장 슬라이드하기 - 변수 초기화를 반복문 앞으로 이동
  var volumnCredits = 0 // 포인트 적립
  for performance in customer.requestPerformance {
    volumnCredits += volumnCreditsFor(performance: performance)
  }
  
  result += "총액: \(totalAmount)원\n"
  result += "적립 포인트: \(volumnCredits)점\n\n"
  return result
}
  • 해당 부분을 함수로 쪼개기
// nested 함수로 추출
func totalVolumeCredits() -> Int { // <- 추가
  return customer.requestPerformance
    .map(self.volumnCreditsFor(performance:))
    .reduce(0, +)
}
// 적용

func getInvoiceInfo(customer: Customer) -> String {
  
  func totalVolumeCredits() -> Int { // <- 추가
    return customer.requestPerformance
      .map(self.volumnCreditsFor(performance:))
      .reduce(0, +)
  }
  
  var totalAmount = 0 // 토탈 비용
  var result = "청구 내역 (고객명: \(customer.name))\n"
  
  for performance in customer.requestPerformance {
    let thisAmount = amountFor(
      genre: performance.playId.genre,
      audienceCount: performance.audienceCount
    )
    
    // 청구 내역 출력
    result += "  \(performance.playId): \(thisAmount)원 \(performance.audienceCount)좌석\n"
    totalAmount += thisAmount
  }

  let volumnCredits = totalVolumeCredits() // <- 적용
  
  result += "총액: \(totalAmount)원\n"
  result += "적립 포인트: \(volumnCredits)점\n\n"
  return result
}

-> 반복문을 여러개로 쪼개서 성능에 영향이 있지만 미비하여, 마틴 파울러는 미비한 성능 부분은 무시를 권장

  • 위에서 중요한 리펙토링 방법: 한꺼번에 함수를 쪼개지 않았고 여러 단계를 통해서 수행
    • 반복문 쪼개기 (변수 값을 누적시키는 부분을 분리)
    • 문장 슬라이드하기 (초기화 문장을 변수 값 누적 코드 바로 앞으로 이동)
    • 함수 추출하기 (적립 포인트 계산 부분을 별도 함수로 추출)

 

* 참고

- Refactoring (Marting Flowler)

Comments