Swift 5.0 Result Type 소개 (에러 처리를 쉽고 깔끔하게 결과는 명확하게 SE-0235)

안녕하세요~! 오늘은 새롭게 소개된 Result Type을 소개해 드리려고합니다.

Result Type은 아래 코드 처럼 enum으로 선언 되어있고

성공과, 실패를 Generic으로 받고 있습니다. 실패는 무조건 에러 타입이구요

@frozen enum Result<Success, Failure> where Failure : Error

기존의 에러처리 방식을 개선하고 결과값을 명확히 받기 위해

Result Type이 나왔다고 보여지는데요.

왜 그런지 아래 예제를 통해 함께 보시죠~!

 

아래 주문을 받는 받았을 때 발생 할 수 있는 간단한 에러를 정의 했습니다.

enum OrderError: Error {
     case LackOfMoney
}

 

메뉴는 햄버거가 좋을거 같아요.

햄버거 종류와 종류에 따른 가격을 받을 수 있습니다.

enum HamburgerMenu {
    case cheezeBurger, bigMac, spicyCrispy
    
    var price: Int {
        switch self {
        case .cheezeBurger:
            return 6_000
        case .bigMac:
            return 5_000
        case .spicyCrispy:
            return 5_500
        }
    }
}

 

주문하는 고객님도 있어야 할거 같아요.

갖고 있는돈과 메뉴를 받아서 주문하는 행위도 추가 했습니다.

돈이 모자르다면 에러를 주고 있어요.

struct Customer {
    var money: Int
    
    func order(hamburgerMenu menu: HamburgerMenu) throws -> Bool {
        if money < menu.price {
            throw OrderError.LackOfMoney
        }
        return true
    }
}

 

고객 한 분이 들어오고 주문을 합니다.

let customer = Customer(money: 2_000)

do {
    try customer.order(hamburgerMenu: .cheezeBurger)
} catch {
    print(error)
}

 

위 코드를 보면서 에러 처리시 보이는 문제점들이 몇 가지 있습니다.

- 고객이 주문할때 에러 발생시 throws로 에러를 던지는데, 이때 에러의 형식을 정할 수 없습니다. (에러 타입에 대한 명확성x)

- 저희가 정의한 에러타입이 아닌 에러 프로토콜을 주고 있어 어떤 에러 타입인지 바로 알 수 없습니다. (do - catch문 안에서)

- 에러를 받는 입장에서는 어떤 에러인지 확인 후, 타입 캐스팅을 통해 정의한 에러를 사용할 수 있습니다.

- 정의한 에러 타입에서 새로운 타입이 추가되어도 컴파일러는 알 수 없어 런타임시(앱 실행중에) 해당 에러를 처리 안했을때 생기는 문제점들이 있을 수 있습니다.

 

 

위에 언급한 문제들이 어떻게 해결되는지

새롭게 발표한 Result Type을 통해서 코드가 어떻게 변경되는지 보시죠~!

 

사용자가 주문 행위 리턴 값이 Result 타입으로 받고 있습니다.

throws로 던졌을때 처리와 비교 했을때 차이점이 보이고 있습니다.

실패했을때 받는 에러와 성공했을때 받는 결과값이 명확하게 보이고, 어떤 에러타입과 결과값을 받을지 알 수 있습니다.

struct Customer {
    var money: Int
    
    func order(hamburgerMenu menu: HamburgerMenu) -> Result<Bool, OrderError> {
        if money < menu.price {
            return .failure(.LackOfMoney)
        }
        return .success(true)
    }
}

 

고객이 주문하는 과정에서도 

do - catch문으로 처리했을때와 result 타입을 받아 switch문으로 처리 했을때도 차이가 있습니다.

성공과 실패 상황으로 극명하게 나뉜 모습이 더 깔끔하고 명확해진 모습을 볼 수 있습니다. 

에러타입에 대해서도 타입 캐스팅도 필요없고, 어떤 에러 타입이 오는지 명확해지죠. 해당 에러에 정의한 타입이 추가되었을때도

컴파일시 해당 타입에 대해 처리되지 않은 부분들을 미리 잡아주기 때문에 코드 안정성도 높아집니다.

let customer = Customer(money: 2000)

let result = customer.order(hamburgerMenu: .cheezeBurger)
switch result {
case .success(let success):
    break
case .failure(let error):
    break
}

// 성공했을 경우, 결과값만 받아서 처리하고 싶을때
if let sucess = try? result.get() {
    
}

 

기존 do - catch + throws을 이용해서 에러 타입을 처리하기 보단 소개해드린 Result Type을 이용해서

해보시는건 어떨까요?.

개인적으로 Result Type으로 정의하지 못한 부분들을 리팩토링 해볼 예정입니다.

오늘은 여기까지 하겠습니다. 감사합니다~!!

 

함께 읽어보면 좋은 글:

 

Swift Enum의 활용 (구조체 또는 클래스 대신 열거형으로 정리해봐요)

Enum은 특정한 이름을 정의하고 값 대신에 사용하는 기능으로 아래와 같이 사용하는 경우가 많습니다. Enum Coffee { case Macchiato case Espresso } Enum에 대해 공부하고 있는데요. 그 중에 실전에서 활용하

leviblog.tistory.com

 

참조:

https://github.com/apple/swift-evolution/blob/main/proposals/0235-add-result.md