조건문 간결화 - ChoDragon9/posts GitHub Wiki

조건문 간결화

조건문 쪼개기(Decompose Conditional)

복잡한 조건문이 있을 때 각 부분을 메서드로 빼내자

// Before
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
  charge = quantity * _winterRate + _winterServiceCharge
} else {
  charge = quantity * _summerRate
}

// After
const notSummer = (date) => date.before(SUMMER_START) || date.after(SUMMER_END)
const winterCharge = (quantity) => quantity * _winterRate + _winterServiceCharge
const summerCharge = (quantity) => quantity * _summerRate

if (notSummer(date)) {
  charge = winterCharge(quantity)
} else {
  charge = summerCharge(quantity)
}

중복 조건식 통합(Consolidate Conditional Experssion)

여러 조건 검사식의 결과가 같을 때 하나의 조건문으로 합친 후 메서드로 빼내자

// Before
const disabilityAmount = () => {
  if (_seniority < 2) return 0
  if (_monthDisabled > 12) return 0
  if (_isPartTime < 2) return 0
}

// After
const isNotEligableForDisability = () => {
  return _seniority < 2 || _monthDisabled > 12 || _isPartTime < 2
}
const disabilityAmount = () => {
  if (isNotEligableForDisability()) return 0
}

조건문의 공통 실행 코드 빼내기(Consolidate Duplicate Conditional Fragments)

조건문의 모든절에 같은 실행 코드가 있을 때 같은 부분을 조건문 밖으로 빼자

// Before
if (isSpecialDeal()) {
  total = price * 0.95
  send()
} else {
  total = price * 0.98
  send()
}

// After
if (isSpecialDeal()) {
  total = price * 0.95
} else {
  total = price * 0.98
}
send()

제어 플래그 제거(Remove Control Frag)

논리 연산식의 제어 플래그 역할을 하는 변수가 있을 때 그 변수를 break문이나 return문으로 바꾸자

// Before
const checkSecurity = (people) => {
  let found = ""
  for (let i = 0; i < people.length; i++) {
    if (!found) {
      if(people[i] === "Don") {
        sendAlert()
        found = "Don"
      }
      if(people[i] === "John") {
        sendAlert()
        found = "John"
      }
    }
  }
  someAfterCode(found)
}

// After
const findMiscreant = (people) => {
  for (let i = 0; i < people.length; i++) {
    if (!found) {
      if(people[i] === "Don") {
        sendAlert()
        return "Don"
      }
      if(people[i] === "John") {
        sendAlert()
        return "John"
      }
    }
  }
  return ""
}
const checkSecurity = (people) => {
  let found = findMiscreant(people)
  someAfterCode(found)
}

여러 겹의 조건문을 감시 절로 전환(Replace Nested Conditional with Guard Clauses)

메서드에 조건문이 있어서 정상적인 실행 결로를 파악하기 힘들 때 모든 특수한 경우에 감시 절을 사용하자

// Before
const getPayAmount = () => {
  let result = null
  if (_isDead) {
    result = deadAmount()
  } else {
    if (_isSeparated) {
      result = separatedAmount()
    } else {
      if (_isRetired) {
        result = retiredAmount()
      } else {
        result = normalPayAmount()
      }
    }
  }
  return result
}

// After
const getPayAmount = () => {
  if (_isDead) {
    return deadAmount()
  } 
  if (_isSeparated) {
    return separatedAmount()
  }
  if (_isRetired) {
    return retiredAmount()
  }
  return normalPayAmount()
}

조건문을 재정의로 전환(Replace Conditional with Polymorphism)

객체 타입에 따라 다른 기능을 실행하는 조건문이 있을 땐 조건문의 각 절을 하위 클래스의 재정의 메서드 안으로 옮기고, 원본 메서드는 abstract 타입으로 수정하자.

// Before
class Employee {
  payAmount () {
    switch (this.getType()) {
      case EmployeeType.ENGINEER:
        return this.getMonthlySalary()
      case EmployeeType.SALESMAN:
        return this.getMonthlySalary() + this.getCommission()
      case EmployeeType.MANAGER:
        return this.getMonthlySalary() + this.getBonus()
    }
  }
  getType () {
    return this.type
  }
  setType (type) {
    this.type = type
  }
}

// After
class Engineer {
  payAmount (emp) {
    return emp.getMonthlySalary()
  }
}
class Salesman {
  payAmount (emp) {
    return emp.getMonthlySalary() + emp.getCommission()
  }
}
class Manager {
  payAmount (emp) {
    return emp.getMonthlySalary() + emp.getBonus()
  }
}
class EmployeeType {
  static newType (type) {
    switch (type) {
      case EmployeeType.ENGINEER:
        return new Engineer()
      case EmployeeType.SALESMAN:
        return new Salesman()
      case EmployeeType.MANAGER:
        return new Manager()
    }
  }
}
class Employee {
  payAmount () {
    return this.state.payAmount(this)
  }
  getType () {
    return this.state
  }
  setType (type) {
    this.state = EmployeeType.newType(type)
  }
}

Null 검사를 Null 객체에 위임(Introduce Null Object)

null 값을 검사하는 코드가 계속 나올 땐 null 값을 널 객체로 만들자

// Before
class Customer { }
class Site {
  getCustomer () {
    return this.customer
  }
  setCustomer () {
    this.customer = new Customer()
  }
}

const customer = new Site().getCustomer()

if (customer === null) plan = BillingPlan.basic()
else plan = customer.getPlan()

if (customer === null) customerName = "occupant"
else customerName = customer.getName()

// After
class NullCustomer {
  isNull () {
    return true    
  }
}
class Customer {
  isNull () {
    return false
  }
}
class Site {
  getCustomer () {
    return this.customer === null ?
      new NullCustomer() :
      this.customer
  }
  setCustomer () {
    this.customer = new Customer()
  }
}

const customer = new Site().getCustomer()

if (customer.isNull()) plan = BillingPlan.basic()
else plan = customer.getPlan()

if (customer.isNull()) customerName = "occupant"
else customerName = customer.getName()

어설션 넣기(Introduce Assertion)

일부 코드가 프로그램의 어떤 상태를 전제할 뗀, 어설션을 넣어서 그 전제를 확실하게 코드로 작성하자

어설션 : 항상 참으로 전제되는 조건문으로 실패하면 프로그래머가 오류를 범한 것으로 예외 통지를 하게 된다.

// Before
const getExpenseLimit = () => {
  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit :
    primaryProject.getMemberExpenseLimit()
}

// After
class Assert {
  static isTrue (val) {
    if (val !== true) throw 'Error'
  }
}
const getExpenseLimit = () => {
  Assert.isTrue(expenseLimit !== NULL_EXPENSE || primaryProject !== null)
  return (expenseLimit != NULL_EXPENSE) ?
    expenseLimit :
    primaryProject.getMemberExpenseLimit()
}