🔥 에러 처리

517자
6분

프로그래밍을 하다 보면 예외 상황이 발생할 수 있어요. 이런 상황을 적절히 처리하는 것이 중요하죠. Swift에서는 Error 프로토콜을 채택한 타입을 사용하여 에러를 표현해요.

swift
enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}
swift
enum PrinterError: Error {
    case outOfPaper
    case noToner
    case onFire
}

위의 코드에서는 PrinterError라는 열거형을 정의하고 있어요. 이 열거형은 프린터 관련 에러를 나타내는데, outOfPaper, noToner, onFire와 같은 케이스를 가지고 있네요.

에러를 던지려면 throw 키워드를 사용하고, 에러를 던질 수 있는 함수라는 것을 표시하기 위해 throws 키워드를 사용해요. 함수 내에서 에러를 던지면, 함수는 즉시 반환되고 함수를 호출한 코드에서 에러를 처리하게 되죠.

swift
func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}
swift
func send(job: Int, toPrinter printerName: String) throws -> String {
    if printerName == "Never Has Toner" {
        throw PrinterError.noToner
    }
    return "Job sent"
}

위의 함수를 살펴볼까요? send(job:toPrinter:) 함수는 Int 타입의 jobString 타입의 printerName을 매개변수로 받아요. 만약 printerName이 "Never Has Toner"라면, PrinterError.noToner 에러를 던지게 되죠. 에러가 발생하지 않으면 "Job sent"라는 문자열을 반환해요.

에러를 처리하는 방법에는 여러 가지가 있어요. 그 중 하나는 do-catch 구문을 사용하는 거예요. do 블록 내에서 에러를 던질 수 있는 코드 앞에 try를 붙이고, catch 블록에서 에러를 처리하죠. 별도의 이름을 지정하지 않으면 에러는 자동으로 error라는 이름을 갖게 돼요.

swift
do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
// "Job sent" 출력
swift
do {
    let printerResponse = try send(job: 1040, toPrinter: "Bi Sheng")
    print(printerResponse)
} catch {
    print(error)
}
// "Job sent" 출력

위의 코드에서는 send(job:toPrinter:) 함수를 호출하고 있어요. 에러가 발생하지 않으면 printerResponse에 "Job sent"가 할당되고, 이를 출력하죠. 에러가 발생하면 catch 블록에서 에러를 출력해요.

특정 에러를 처리하기 위해 여러 개의 catch 블록을 제공할 수도 있어요. switch문의 case처럼 catch 뒤에 패턴을 작성하면 되죠.

swift
do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
// "Job sent" 출력
 
swift
do {
    let printerResponse = try send(job: 1440, toPrinter: "Gutenberg")
    print(printerResponse)
} catch PrinterError.onFire {
    print("I'll just put this over here, with the rest of the fire.")
} catch let printerError as PrinterError {
    print("Printer error: \(printerError).")
} catch {
    print(error)
}
// "Job sent" 출력
 

위의 코드에서는 PrinterError.onFire 에러를 별도로 처리하고 있어요. 그 외의 PrinterError 에러는 printerError라는 이름으로 바인딩하여 처리하고 있죠. 마지막 catch 블록은 그 외의 모든 에러를 처리해요.

에러를 처리하는 또 다른 방법은 try?를 사용하여 결과를 옵셔널로 변환하는 거예요. 함수가 에러를 던지면 특정 에러는 무시되고 결과는 nil이 되죠. 에러가 발생하지 않으면 함수가 반환한 값을 담은 옵셔널이 결과가 돼요.

swift
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")
swift
let printerSuccess = try? send(job: 1884, toPrinter: "Mergenthaler")
let printerFailure = try? send(job: 1885, toPrinter: "Never Has Toner")

위의 코드에서 printerSuccess는 옵셔널 문자열이 되고, printerFailurenil이 되겠죠?

defer 키워드를 사용하면 함수의 다른 모든 코드가 실행된 후, 함수가 반환되기 직전에 실행되는 코드 블록을 작성할 수 있어요. 함수가 에러를 던지는지 여부와 관계없이 코드가 실행되죠. defer를 사용하면 설정 코드와 정리 코드를 나란히 작성할 수 있어요. 실행 시점은 달라도 말이죠.

swift
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
 
func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }
 
    let result = fridgeContent.contains(food)
    return result
}
 
if fridgeContains("banana") {
    print("Found a banana")
}
print(fridgeIsOpen)
// "false" 출력
swift
var fridgeIsOpen = false
let fridgeContent = ["milk", "eggs", "leftovers"]
 
func fridgeContains(_ food: String) -> Bool {
    fridgeIsOpen = true
    defer {
        fridgeIsOpen = false
    }
 
    let result = fridgeContent.contains(food)
    return result
}
 
if fridgeContains("banana") {
    print("Found a banana")
}
print(fridgeIsOpen)
// "false" 출력

위의 코드에서 fridgeContains(_:) 함수는 fridgeIsOpentrue로 설정한 후, defer 블록에서 fridgeIsOpen을 다시 false로 설정하고 있어요. 함수가 반환되기 직전에 defer 블록이 실행되므로, fridgeIsOpen은 항상 false로 설정되겠죠?

에러 처리는 프로그래밍에서 매우 중요해요. Swift는 에러 처리를 위한 다양한 방법을 제공하고 있죠. 상황에 맞는 적절한 에러 처리 방식을 선택하여 사용하면 됩니다. 그러면 더 안정적이고 예외 상황에 잘 대응할 수 있는 코드를 작성할 수 있을 거예요!

YouTube 영상

채널 보기
우리가 매일 쓰는 맞춤법 검사기와 라우터 속에 숨겨진 알고리즘은? | Trie 자료구조 이야기
AI를 위한 선형대수학 - 소개 | 선형대수학
벡터의 정의와 덧셈 연산 | 선형대수학
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
Trie 자료구조 완전 정복 - 개념부터 시각화까지 | Trie 자료구조 이야기
스칼라 곱셈과 내적의 기하학적 의미 | 선형대수학
트라이(Trie)를 이용한 자동 완성 알고리즘 | Trie 자료구조 이야기