backend

Go 언어 에러 처리 방식 정리: error 인터페이스와 실전 패턴

mirabo01 2026. 1. 15. 21:56

인터페이스까지 이해했다면, 이제 Go를 Go답게 만드는 핵심 주제인
에러 처리(error handling) 를 살펴볼 차례다.

Go의 에러 처리는 종종 “번거롭다”, “코드가 길어진다”는 평가를 받는다.
하지만 실제로 사용해보면, 이 방식이 에러를 숨기지 않고 드러내는 데 초점을 두고 있다는 걸 알게 된다.

이 글에서는

  • Go의 error 인터페이스 구조
  • 에러를 처리하는 기본 패턴
  • 실무에서 자주 쓰는 설계 방식

을 중심으로 정리한다.


Go에서 error는 인터페이스다

Go에서 에러는 내장 타입이 아니라 인터페이스다.

type error interface {
    Error() string
}
  • Error() 메서드를 구현하면 모두 error가 된다
  • 에러는 값(value)처럼 전달된다

이 구조 덕분에,
Go의 에러는 예외(exception)가 아니라 명시적인 반환값으로 다뤄진다.


기본적인 에러 처리 패턴

Go에서 가장 흔히 보는 형태는 다음과 같다.

result, err := doSomething()
if err != nil {
    return err
}
  • 에러가 발생하면 즉시 처리하거나 반환
  • 정상 흐름과 에러 흐름이 명확히 분리된다

실제로 써보면,
에러를 나중에 몰아서 처리하기보다는 발생 지점 근처에서 바로 다루는 방식이 된다.


errors.New와 fmt.Errorf

에러를 만드는 가장 기본적인 방법이다.

import "errors"

err := errors.New("invalid input")
err := fmt.Errorf("user id %d not found", id)
  • errors.New: 고정된 메시지
  • fmt.Errorf: 컨텍스트 정보를 포함하기 좋음

실무에서는 대부분 fmt.Errorf를 더 자주 사용하게 된다.


에러 래핑(Error Wrapping)

Go 1.13 이후에는 에러 래핑이 표준 패턴으로 자리 잡았다.

return fmt.Errorf("read config failed: %w", err)
  • %w를 사용하면 기존 에러를 감싼다
  • 원본 에러 정보가 유지된다

이 방식 덕분에,
상위 레이어로 에러를 전달하면서도 맥락(context) 을 잃지 않을 수 있다.


errors.Is와 errors.As

래핑된 에러를 판별할 때 사용한다.

if errors.Is(err, ErrNotFound) {
    // 특정 에러 처리
}
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    // 타입 기반 에러 처리
}
  • Is: 값 비교
  • As: 타입 비교

에러 메시지 문자열을 비교하던 과거 방식보다 훨씬 안전하다.


panic과 recover는 언제 쓰나

panic("something went wrong")

Go에도 panic은 존재하지만,
일반적인 에러 처리 수단은 아니다.

  • 복구 불가능한 상황
  • 프로그램이 정상 상태를 유지할 수 없는 경우

에서만 제한적으로 사용한다.

defer func() {
    if r := recover(); r != nil {
        fmt.Println("recovered:", r)
    }
}()

실무에서는

  • 라이브러리 내부 보호용
  • 최상위(main, goroutine entry) 안전장치

정도로만 사용하는 편이 일반적이다.


커스텀 에러 타입 만들기

에러를 좀 더 구조적으로 다루고 싶을 때는
커스텀 에러 타입을 정의한다.

type NotFoundError struct {
    ID int
}

func (e NotFoundError) Error() string {
    return fmt.Sprintf("id %d not found", e.ID)
}
  • 단순 문자열보다 의미가 분명해진다
  • errors.As로 타입 기반 분기가 가능하다

다만,
에러 타입이 과도하게 늘어나면 관리가 어려워질 수 있으니 주의가 필요하다.


Go 에러 처리의 철학

Go의 에러 처리는
숨기지 않고, 명시적으로 다루는 것을 목표로 한다.

  • 예외 흐름이 없다
  • 호출 스택을 따라 에러가 드러난다
  • 코드만 봐도 실패 가능성이 보인다

초반에는 코드가 길어 보일 수 있다.
하지만 규모가 커질수록,
“어디서 문제가 발생했는지”를 추적하기는 훨씬 수월해진다.


이런 경우에 잘 맞는다

  • 안정성과 예측 가능성이 중요한 서버 코드
  • 에러 흐름을 명확히 관리해야 하는 프로젝트
  • 팀 단위 협업에서 디버깅 비용을 줄이고 싶은 경우

반대로,
간단한 스크립트나 빠른 프로토타이핑에서는
다소 번거롭게 느껴질 수도 있다.

다음 글에서는 이 에러 처리와 자연스럽게 이어지는 주제인
defer 문과 자원 관리 패턴을 다루면 흐름이 좋다.