[Swift] Result Type

Kio·2022년 2월 14일
8

Swift

목록 보기
13/14
post-thumbnail

안녕하세요! Kio입니다 👻

지난 번 에러처리(Error Handling) 를 다룬 적이 있었는데요.
오늘은 그 이후에 도입된 Result Type 에 대해 알아보고자 합니다.

Let's get started 🥰


Why Result Type?

그렇다면 왜 Result Type 이 나온 걸까요?
라떼는... 그런 거 없었는데.... 도대체 왜...? 😱


기존 에러처리 방법

  1. 열거형을 선언하고
enum McDonaldOrderError: Error {
     case invalidSelection
     case LackOfMoney
     case outOfStock
}

  1. 필요한 function 을 만들고
struct Hamburger {
    var name: String
    var price: Int
    var count: Int
}

let bigMac = Hamburger(name: "BigMac", price: 4600, count: 3)
let myMoney = 4000

func OrderMcDonaldMenu(orderedMenu: Hamburger) throws {
    if orderedMenu.name != "BigMac" {
        throw McDonaldOrderError.invalidSelection
    }
    if orderedMenu.price > myMoney {
        throw McDonaldOrderError.LackOfMoney
    }
    if orderedMenu.count == 0 {
        throw McDonaldOrderError.outOfStock
    }
}

  1. do 에서 try 를 던지고, 오류 발생 시 catch 에서 해줄 역할을 정의
do {
    try OrderMcDonaldMenu(orderedMeun: bigMac)
} catch McDonaldOrderError.invalidSelection {
    print("저희 매장에 주문한 메뉴가 없습니다. 메뉴이름을 다시 확인해주세요.")
} catch McDonaldOrderError.LackOfMoney {
    print("메뉴를 주문하기에 고객님의 잔액이 부족합니다.")
} catch McDonaldOrderError.outOfStock {
    print("현재 재고가 없어 주문이 불가능합니다.")
}

위 1-3 번의 과정을 거쳐야 에러처리가 가능했습니다.
이러한 에러처리의 단점은 무엇이 있을까요?


에러처리 단점

  • 3단계의 과정을 거치다 보니 function 에서
    Error 를 던지기만 하고 어떤 과정으로 이어지는지 알길이 없습니다.
    do-catch 과정을 또 봐야합니다...
    (마치 택배를 보냈는데 도착지는 모르고 일단 옥천 hub에 갇힌 느낌?) 😱
  • throws 키워드는 에러를 던진다는 뜻이지만, 어떤 에러를 던지는지 특정하기 어렵습니다.
  • (단점은 점점 추가하겠습니다!?)


그래서 이를 개선하고자 나온 게 Result Type 입니다.
그럼 이게 얼마나 더 괜찮은지 알아볼까요?!




Result

정의 (Definition)

Generic Enumeration

A value that represents either a success or a failure, including an associated value in each case.

Result 타입은 Generic Enumeration로 선언되어 있고,
경우에 따른 연관값을 포함하여, 성공과 실패를 나타내는 값입니다.


선언 (Declaration)

Apple 에서는 Result 가 아래처럼 정의되어 있네요.

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



예시 (Discussion)

정의와 선언만으로는 이해가 가지 않습니다 😱
저에겐 항상 예시가 필요해요...😭


기존코드

enum McDonaldOrderError: Error {
     case invalidSelection
     case LackOfMoney
     case outOfStock
}

struct Hamburger {
    var name: String
    var price: Int
    var count: Int
}

let bigMac = Hamburger(name: "BigMac", price: 4600, count: 3)
let myMoney = 4000

func OrderMcDonaldMenu(orderedMeun: Hamburger) throws {
    if orderedMeun.name != "BigMac" {
        throw McDonaldOrderError.invalidSelection
    }
    if orderedMeun.price > myMoney {
        throw McDonaldOrderError.LackOfMoney
    }
    if orderedMeun.count == 0 {
        throw McDonaldOrderError.outOfStock
    }
}

do {
    try OrderMcDonaldMenu(orderedMeun: bigMac)
} catch McDonaldOrderError.invalidSelection {
    print("저희 매장에 주문한 메뉴가 없습니다. 메뉴이름을 다시 확인해주세요.")
} catch McDonaldOrderError.LackOfMoney {
    print("메뉴를 주문하기에 고객님의 잔액이 부족합니다.")
} catch McDonaldOrderError.outOfStock {
    print("현재 재고가 없어 주문이 불가능합니다.")
}

Result Type을 적용한 코드

enum McDonaldOrderError: Error {
     case invalidSelection
     case LackOfMoney
     case outOfStock
}

struct Hamburger {
    var name: String
    var price: Int
    var count: Int
}

let bigMac = Hamburger(name: "BigMac", price: 4600, count: 3)
let myMoney = 4000

// 🛑🛑🛑🛑🛑 여기서부터 코드가 달라집니다 
func orderMcDonaldMenu(orderedMenu: Hamburger) -> Result<Bool, McDonaldOrderError> {
    if orderedMenu.name != "BigMac" {
        return .failure(.invalidSelection)
    }
    if orderedMenu.price > myMoney {
        return .failure(.LackOfMoney)
    }
    if orderedMenu.count == 0 {
        return .failure(.outOfStock)
    }
    
    return .success(true)
}

let isOrderable = orderMcDonaldMenu(orderedMenu: bigMac)
switch isOrderable {
case .success(let data):
    print(data)
case .failure(let error):
    print(error)
}

차이점

차이가 보이시나요?!

기존에 orderMcDonaldMenu 에서 throws 키워드를 선언하고 Error를 던졌었죠.

func OrderMcDonaldMenu(orderedMenu: Hamburger) throws {
	// code
}

Result Type을 적용한 곳에서는 Result 라는 걸 return 하고 있는 걸 볼 수 있습니다.

func orderMcDonaldMenu(orderedMenu: Hamburger) -> Result<Bool, McDonaldOrderError> { 
	// code
}

Result Type을 사용하고자 한다면
성공했을 경우와, 실패했을 경우의 값을 넣어줘야합니다.

Result<Bool, McDonaldOrderError> 

이렇게 넣어줬다면 성공하면 Bool타입인 true or false를 받고
실패하게 된다면 제가 미리 선언한 Error 열거형으로 반환합니다.


let isOrderable = orderMcDonaldMenu(orderedMenu: bigMac)
switch isOrderable {
case .success(let data):
    print(data)
case .failure(let error):
    print(error)
}

그리고 Result Type은 Result<Success, Failure> 으로 이루어졌기 때문에
switch문을 통해 success, failure 의 경우에 따라 처리를 해주면 됩니다!

(이 부분이 이해가 안 간다면 위에 선언부분을 다시 한번 확인해주세요)




Result Method - get()

switch문은 성공과 실패의 경우를 모두 다룬 건데요
성공했을 때만 다루는 방법도 있답니다!

if let result = try? isOrderable.get() {
    print(result)
}

위처럼 작성한다면 성공했을 때만 실행이 가능합니다.

....? 🤔 😱 👀
무슨 말인지는 지금 다시 설명할게요!



정의 (Definition)

Returns the success value as a throwing expression.

성공했을 경우만 throw 하는 표현식으로 리턴합니다.



선언 (Declaration)

Apple 에서는 Result Method get() 이 아래처럼 정의되어 있네요.

func get() throws -> Success



예시 (Discussion)

if let result = try? isOrderable.get() {
    print(result)
}

다시 이 코드로 돌아왔네요!

switch문에서는 성공과 실패일 경우를 모두 다뤘었는데
get() 은 성공일 경우만 다루는 거에요.
실패의 경우는 저 구문을 타지 않으니 버리는거죠!

어느 쪽이 더 좋다라기보다는 Result Type 에서
처리하는 방법이 다양하다 라고 알아두시면 좋겠네요👻




마치면서

이렇게 Result Type 에 대해 알아보았는데요!
기존 Error Handling 과 어떤 점이 다른 지 기억하면 좋을 것 같네요 👻

throwing 형식의 함수는 어떤 Error 를 던지는 지 알기 어렵습니다. do-catch 문만 본다면 더 어떤 상황에 이런 Error 를 던지는지도 알기 어렵죠 😱

반면 ResultError 형식이 선언되고, 결과를 성공과 실패로 나누어 처리한다는 점에서 가독성이 더 좋지 않을까 생각이 되네요 🥳

또 하나의 적용할 방법을 알게 되어 기분이 좋습니다 🥰




참고

Result - developer.apple
Result get() - developer.apple




잘못된 정보가 있으면 언제든 코멘트 부탁드립니다 👻

profile
Someday_iOS_Dev

1개의 댓글

comment-user-thumbnail
2022년 9월 7일

좋은 글 잘 봤습니다 Kio :)

답글 달기