Clean Code 책의 오류 처리 챕터를 읽고 정리한 내용입니다.
뭔가 잘못될 가능성은 늘 존재한다. 뭔가 잘못되면 바로 잡을 책임은 바로 우리 프로그래머에게 있다.
오류 처리는 중요 -> 오류 처리 때문에 프로그램 논리 이해 어렵 -> 깨끗한 코드 X
오류를 정해서 보여주는 방식을 많이 했는데 이건 코드가 복잡해짐 -> 함수를 호출한 즉시 오류를 확인해야 함
그래서 오류가 발생하면 예외를 던지는게 나음
논리와 오류 처리 코드가 섞이지 않음
enum CalculatorError: Error {
case divideByZero
}
struct Calculator {
func divide(num1: Int, num2: Int) -> Result<Int, CalculatorError> {
if num2 == 0 {
return .failure(.divideByZero)
} else {
return .success(num1 / num2)
}
}
}
let calculator = Calculator()
let result = calculator.divide(num1: 10, num2: 0)
switch result {
case .success(let value):
print("결과: \(value)")
case .failure(let error):
print("오류: \(error)")
}
func divide(_ num1: Int, by num2: Int) throws -> Int {
guard num2 != 0 else {
throw DivisionError.divideByZero // 예외 던지기
}
return num1 / num2 // 정상적인 결과 반환
}
do {
let result = try divide(10, by: 0)
print("결과: \(result)")
} catch DivisionError.divideByZero {
print("오류: 0으로 나눌 수 없습니다.")
} catch {
print("알 수 없는 오류 발생: \(error)")
}
범위를 정한다
try 블록에서 무슨 일 이 생기던 catch 블록은 상태를 일관성 있게 유지해야 함
Try-Catch-Finally 문부터 작성하면 try 블록에서 무슨 일이 생기던 호출자가 기대하는 상태를 정의하기 쉬워짐
확인된 예외(Checked Exception)
: 컴파일러가 강제로 처리하도록 하는 예외를 의미합니다
- 메서드가 호출될 때 반드시 try-catch 블록으로 처리되어야 합니다.
확인되지 않은 예외(Unchecked Exception)
: 개발자의 실수나 예상치 못한 상황등으로 발생하는 예외를 의미합니다.- 컴파일러가 예외 처리 코드를 강제하지 않는 예외
확인된 예외는 OCP 위반이다!
여외를 던질 땐 전후 상황을 덧붙여야 오류가 발생한 원인과 위치를 찾기 쉬움
오류 메시지에 정보를 담아 예외와 함께 던져라
오류를 정의할 때 가장 중요한 관심사는 오류를 잡아내는 방법이어야 한다
라이브러리 오류를 주루룩 다 잡아내지 말고 감싸서 예외를 잡아 던지는 클래스를 만들면 간단
클래스나 객체가 예외 상황을 캡슐화해서 처리하도록
= 특수 사례 패턴 (Special case pattern)
nul 확인하는거 얼마나 번거롭고 못잡으면 어떻게 되겟니
nul을 만환하게 하지 말고 예외나 특수 객체를 반환하도록 해라
// 잘못된 예: null을 반환하는 경우
func findUsernameById(userId: Int) -> String? {
if userId == 1 {
return "John"
} else if userId == 2 {
return "Doe"
} else {
return nil // 잘못된 예: nil 반환
}
}
// 올바른 예: nil을 반환하지 않고 예외를 던지는 경우
func findUsernameById(userId: Int) throws -> String {
if userId == 1 {
return "John"
} else if userId == 2 {
return "Doe"
} else {
throw UserNotFoundError.userNotFound("사용자를 찾을 수 없습니다.") // 예외 던지기
}
}
nul을 인수로 전달하지 말고 그 전에 잡거나 예외로 처리
// 잘못된 예: nil을 전달하는 경우
func processOrder(order: Order?) {
guard let order = order else {
// 잘못된 주문 처리 로직
return
}
// 주문 처리 로직
}
// 올바른 예: nil을 전달하지 않고 처리하는 경우
func processOrder(order: Order) {
// 주문 처리 로직
}
오류처리를 논리와 분리라면 튼튼하고 깨끗한 코드 작성 가능
-> 독립적인 추론이 가능해지고 코드의 유지보수성도 크게 높아짐