Go 언어 에러 처리 방식 정리: error 인터페이스와 실전 패턴
인터페이스까지 이해했다면, 이제 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 문과 자원 관리 패턴을 다루면 흐름이 좋다.