Learning
레슨 4 / 8·20분

에러 처리 패턴

Go의 에러 처리 철학

Go는 예외(exception) 대신 에러 값을 반환하는 방식을 사용합니다. 함수가 에러를 반환하면 호출자가 명시적으로 처리해야 합니다. 이 방식은 에러 처리를 강제하여 더 안정적인 코드를 만듭니다.

go
package main

import (
    "errors"
    "fmt"
    "strconv"
)

// 에러를 반환하는 함수
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("0으로 나눌 수 없습니다")
    }
    return a / b, nil
}

func main() {
    // 기본 에러 처리 패턴
    result, err := divide(10, 3)
    if err != nil {
        fmt.Println("에러:", err)
        return
    }
    fmt.Printf("결과: %.2f\n", result)

    // 에러 발생 시
    _, err = divide(10, 0)
    if err != nil {
        fmt.Println("에러:", err)  // 에러: 0으로 나눌 수 없습니다
    }

    // 표준 라이브러리의 에러 처리
    num, err := strconv.Atoi("abc")
    if err != nil {
        fmt.Println("변환 에러:", err)
    } else {
        fmt.Println("숫자:", num)
    }
}

커스텀 에러 타입

error 인터페이스를 구현하면 커스텀 에러 타입을 만들 수 있습니다. 에러에 추가 정보를 담아 더 풍부한 에러 처리가 가능합니다.

go
package main

import (
    "errors"
    "fmt"
)

// 커스텀 에러 타입
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("필드 %s: %s", e.Field, e.Message)
}

func validateAge(age int) error {
    if age < 0 || age > 150 {
        return &ValidationError{
            Field:   "age",
            Message: "유효하지 않은 나이입니다",
        }
    }
    return nil
}

// 에러 래핑 (Go 1.13+)
func processUser(age int) error {
    err := validateAge(age)
    if err != nil {
        return fmt.Errorf("사용자 처리 실패: %w", err)
    }
    return nil
}

func main() {
    err := processUser(200)
    if err != nil {
        fmt.Println(err)  // 사용자 처리 실패: 필드 age: 유효하지 않은 나이입니다

        // errors.As로 원본 에러 타입 확인
        var valErr *ValidationError
        if errors.As(err, &valErr) {
            fmt.Println("필드:", valErr.Field)
        }
    }
}

defer, panic, recover

go
package main

import "fmt"

// defer - 함수 종료 시 실행 (LIFO 순서)
func fileExample() {
    fmt.Println("파일 열기")
    defer fmt.Println("파일 닫기")  // 함수 끝에서 실행
    fmt.Println("파일 작업 중...")
    // 출력: 파일 열기 -> 파일 작업 중... -> 파일 닫기
}

// panic과 recover
func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("패닉 복구: %v", r)
        }
    }()
    return a / b, nil  // b=0이면 panic 발생
}

func main() {
    fileExample()

    result, err := safeDivide(10, 0)
    if err != nil {
        fmt.Println(err)  // 패닉 복구: runtime error: ...
    } else {
        fmt.Println(result)
    }
}
  • error -- Go의 에러 인터페이스 (Error() string)
  • errors.New() -- 단순 에러 생성
  • fmt.Errorf("%w", err) -- 에러 래핑
  • errors.Is() -- 에러 값 비교
  • errors.As() -- 에러 타입 변환
  • defer -- 함수 종료 시 실행할 코드 예약
💡

Go에서 panic은 프로그램 버그와 같은 복구 불가능한 상황에서만 사용하세요. 일반적인 에러는 항상 error 값으로 반환하는 것이 관례입니다.