오류 처리

Judy·2024년 3월 28일

Clean Code 책의 오류 처리 챕터를 읽고 정리한 내용입니다.

뭔가 잘못될 가능성은 늘 존재한다. 뭔가 잘못되면 바로 잡을 책임은 바로 우리 프로그래머에게 있다.

오류 처리는 중요 -> 오류 처리 때문에 프로그램 논리 이해 어렵 -> 깨끗한 코드 X

1. 오류 코드보다는 예외를 사용하라

오류를 정해서 보여주는 방식을 많이 했는데 이건 코드가 복잡해짐 -> 함수를 호출한 즉시 오류를 확인해야 함
그래서 오류가 발생하면 예외를 던지는게 나음
논리와 오류 처리 코드가 섞이지 않음

오류 코드

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)")
}

2. Try-Catch-Finally 문부터 작성하라

범위를 정한다
try 블록에서 무슨 일 이 생기던 catch 블록은 상태를 일관성 있게 유지해야 함
Try-Catch-Finally 문부터 작성하면 try 블록에서 무슨 일이 생기던 호출자가 기대하는 상태를 정의하기 쉬워짐

3. 미확인 예외를 사용해라

확인된 예외(Checked Exception)
: 컴파일러가 강제로 처리하도록 하는 예외를 의미합니다

  • 메서드가 호출될 때 반드시 try-catch 블록으로 처리되어야 합니다.
    확인되지 않은 예외(Unchecked Exception)
    : 개발자의 실수나 예상치 못한 상황등으로 발생하는 예외를 의미합니다.
  • 컴파일러가 예외 처리 코드를 강제하지 않는 예외

확인된 예외는 OCP 위반이다!

  • Swift에서는 미확인(unchecked) 예외와 확인된(checked) 예외를 명시적으로 구분하지는 않음 => Swift에서 적용되는 내용은 아님

4 예외에 의미를 제공하라

여외를 던질 땐 전후 상황을 덧붙여야 오류가 발생한 원인과 위치를 찾기 쉬움
오류 메시지에 정보를 담아 예외와 함께 던져라

5 호출자를 고려해 예외 클래스를 정의하라

오류를 정의할 때 가장 중요한 관심사는 오류를 잡아내는 방법이어야 한다
라이브러리 오류를 주루룩 다 잡아내지 말고 감싸서 예외를 잡아 던지는 클래스를 만들면 간단

  • 예외 클래스의 이름은 해당 예외가 발생한 원인이나 상황을 명확하게 전달

6 정상흐름을 정의하라

클래스나 객체가 예외 상황을 캡슐화해서 처리하도록
= 특수 사례 패턴 (Special case pattern)

7 nil을 반환하지 마라

nul 확인하는거 얼마나 번거롭고 못잡으면 어떻게 되겟니
nul을 만환하게 하지 말고 예외나 특수 객체를 반환하도록 해라

  • 메서드가 null을 반환하면 호출자가 이를 적절하게 처리하지 않을 수 있으며
  • 이로 인해 NullPointerException 등의 오류가 발생할 수 있습니다
  • 메서드가 유효한 값을 반환하도록 설계하고, 예외를 던지거나 옵셔널 등의 다른 방법을 사용하여 예외 상황을 처리하세요.
// 잘못된 예: 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("사용자를 찾을 수 없습니다.") // 예외 던지기
    }
}

8 nil을 전달하지 마라

nul을 인수로 전달하지 말고 그 전에 잡거나 예외로 처리

  • 매개변수로 null을 전달하는 것을 피하라는 것을 의미
  • 메서드에 null을 전달하면 그 메서드가 예상하지 않은 동작을 할 수 있습니다.
  • 메서드가 null을 전달받을 수 있는 경우에 대해 명시적으로 처리하고, null을 전달 << null을 허용하지 않도록 설계
// 잘못된 예: nil을 전달하는 경우
func processOrder(order: Order?) {
    guard let order = order else {
        // 잘못된 주문 처리 로직
        return
    }
    // 주문 처리 로직
}

// 올바른 예: nil을 전달하지 않고 처리하는 경우
func processOrder(order: Order) {
    // 주문 처리 로직
}

결론

오류처리를 논리와 분리라면 튼튼하고 깨끗한 코드 작성 가능
-> 독립적인 추론이 가능해지고 코드의 유지보수성도 크게 높아짐

profile
iOS Developer

0개의 댓글