Special Case Pattern

Lily·2022년 8월 7일
0

Design Pattern

목록 보기
1/1

Special Case Pattern (특수 사례 패턴)


클래스를 만들거나 객체를 조작해 특수 사례를 처리하는 방식

클린 코드에서 “정상 흐름을 정의하라"(138p)라는 파트에서 소개된 패턴입니다.

이 패턴이 어떻게 구현되는지, 스위프트에선 어떻게 작성해볼 수 있을지 궁금해졌습니다.
찾아보니 리팩터링(마틴 파울러 저)이라는 책에서도 소개된 패턴이더라구요.
리팩터링을 정리한 다른 블로그 글을 참고해보니 다양한 방법으로 구현이 될 수 있더군요!

방법 중 서브클래싱을 이용한 방법이 클린 코드 책에서 예시로 든 비용 청구 애플리케이션의 비용 계산 코드와 동일했습니다. 그래서 해당 코드를 스위프트로 바꾸어 작성해보고 패턴의 구현 방법과 특징에 대해 알아보도록 할게요~!



문제 상황

  • nil을 반환할 수 있는 코드에서는 런타임 에러가 발생할 수 있습니다.
  • 따라서 nil을 체크하는 조건문을 작성해서 런타임 에러를 방지해야합니다
  • 그런데 이러한 조건문은 많은 곳에서 중복적인 코드를 만들어냅니다.

또는

  • 에러를 던지는 특이 케이스의 경우에도 호출자에서 try catch문을 중복해서 사용하게 됩니다.
  • 그리고 이러한 코드는 논리를 따라가기 어렵게 만듭니다.

위 상황이 중복적으로 발생한다면 특이 케이스 패턴을 도입을 고민해볼 수 있습니다!


예시 코드를 볼게요

비용을 기록하는 ExpenseReportDAO 라는 타입이 있습니다.
직원이 식대 비용을 청구하면 Expense라는 타입을 반환하고,
청구하지 않아 ID가 mealExpenseForEmployee에 없으면 mealExpenseNotFound에러를 던집니다.

class ExpenseReportDAO {
    
    var mealExpenseForEmployee: [Int: Expense] = [:] 
    
    func getMeals(of employeeID: Int) throws -> Expense {
        // 비용을 청구한 경우
        if let expense = mealExpenseForEmployee[employeeID] {
            return  expense
        }
        // 비용을 청구하지 않은 경우
        throw ExpenseReportError.mealExpenseNotFound
    }
    
}

enum ExpenseReportError: Error {

    case mealExpenseNotFound
}


class Expense {
    
    func getTotal() -> Int {
        // 비용의 총합을 리턴
        return 10000
    }
    
}

위 함수를 호출하는 코드입니다.
mealExpenseNotFound에러가 발생하면 일일 기본 식비를 총계에 더합니다.


let exportReportDAO = ExpenseReportDAO()
var total = 0

do {
    let expense = try exportReportDAO.getMeals(of: 5) 
    total += expense.getTotal()
} catch ExpenseReportError.mealExpenseNotFound {
    total += getDailyMealExpense() // 일일 기본 식비 리턴
}

try catch문을 사용해서 가독성이 떨어지고 작가의 말을 빌리면 논리를 따라가기 힘들게 만드네요..!
Special Case Pattern을 사용해서 개선해보도록 할게요~


💡 Special Case Pattern을 사용하면?

방법

nil 을 반환하거나 이상한 값(에러)을 반환하는 대신 정상 객체의 서브 클래스 special case를 반환한다.

  • 중복해서 나타나는 특이 케이스에 대해 서브클래스 생성
class BasicDailyMealExpense: Expense {
    
    func getTotal() -> Int {
        // 일일 기본 식비를 반환
        return 9000
    }
    
}
  • 특이 케이스가 발생하는 상황에서 특이 케이스 서브클래스를 리턴
class ExpenseReportDAO {
    
    func getMeals(of employeeID: Int) -> Expense {
        if let expense = mealsForEmployee[employeeID] {
            return  expense
        }
        return  BasicDailyMealExpense()
    }
    
}
  • 호출자에서 예외 처리를 하지 않고, 동일한 인터페이스 사용
let expense = exportReportDAO.getMeals(of: 5) 
total += expense.getTotoal()

효과

  • 호출자에서 예외를 확인하지 않고 단순하게 함수를 호출할 수 있다.
  • 특히 많은 클라이언트 단에서 예외를 발생시키는 함수를 사용한다고 했을 때, 중복 코드를 방지할 수 있다
  • 가독성이 올라간다
  • nil로 인한 런타임 에러 방지~ (Swift에서는 옵셔널 체이닝 제공되기 때문에, 더 안전한 코드를 작성할 수 있다)

정리

모든 에러 처리나 특이 케이스에 적용할 수 있는 패턴은 아니라고 생각했습니다.
nil 이나 에러대신 반환 할 수 있는 대체값이 있는 경우에만 사용할 수 있기 때문입니다.

하지만, 적용이 가능한 상황이라면 적극! 도입해보고 싶네요!!!


References

마틴 파울러 블로그에 소개된 Special Case
리팩터링 책에 소개된 SpecialCasePattern을 정리한 블로그

profile
i🍎S 개발을 합니다

1개의 댓글

comment-user-thumbnail
2023년 2월 22일

잘봤습니다~

답글 달기