Menu

Golang 오류: 감싸기, errors.Is, errors.As, 커스텀 타입

센티널 오류와 커스텀 오류 타입을 정의하고, %w로 오류를 감싸고, errors.Is와 errors.As로 확인하고, errors.Join으로 여러 개를 합치고, 필요할 때 Unwrap과 Is 메서드를 작성하는 법을 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

세 종류의 오류

Go 코드는 가장 단순한 것부터 가장 풍부한 것까지 세 가지 모양의 오류를 씁니다:

  1. 즉석 오류: 그 자리에서 만드는 errors.New("...")fmt.Errorf("..."). 호출자는 메시지만 읽을 수 있습니다.
  2. 센티널 오류: io.EOF 같은 패키지 수준 변수. 호출자가 errors.Is로 확인할 수 있습니다.
  3. 오류 타입: 필드를 담고 Error() 메서드가 있는 구조체. 호출자가 errors.As로 꺼내서 필드를 읽을 수 있습니다.

호출자가 필요한 일을 할 수 있게 해 주는 것 중 가장 단순한 것을 고르세요.

센티널 오류

센티널은 한 번 선언하고 동일성으로 비교하는 오류 값입니다. 관례상 이름은 Err로 시작합니다.

출력:

mug bought
cap is out of stock, notify me later
buy "hat": not found

같은 텍스트로 errors.New를 두 번 호출하면 서로 다른 오류가 만들어집니다. errors.New("x") == errors.New("x")false입니다. 그래서 센티널은 공유되는 변수 하나여야 합니다.

센티널은 패키지 API의 일부가 됩니다. 호출자가 ErrNotFound를 확인하기 시작하면, 그들을 깨뜨리지 않고는 그 오류를 더 이상 반환하지 않을 수 없습니다. 호출자가 정말로 분기해야 하는 것만 공개하세요.

커스텀 오류 타입

호출자에게 세부 정보(어느 필드인지, 어떤 상태 코드인지, 재시도 지연은 얼마인지)가 필요하다면 타입을 정의하세요.

출력:

register: age: must be between 0 and 150
bad field: age

메서드는 포인터 리시버이고 함수는 &ValidationError{...}를 반환하므로, errors.As의 대상은 *ValidationError이고 그 주소(&ve, 즉 **ValidationError)를 넘깁니다. 이 단계를 틀리는 것이 errors.As의 전형적인 실수입니다. 값 리시버라면 ValidationError{...}를 반환하고 var ve ValidationError를 선언했을 것입니다. &ve 대신 ve를 넘기는 가장 흔한 실수는 go vet이 잡아 줍니다: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type.

%w로 감싸기

%w와 함께 쓴 fmt.Errorf는 감싼 오류를 기억하는 오류를 반환합니다. 각 계층이 맥락을 더하면서 체인을 온전히 유지합니다.

출력:

start: connect db: timeout
  *fmt.wrapError: start: connect db: timeout
  *fmt.wrapError: connect db: timeout
  *errors.errorString: timeout
true
false

%v를 쓰면 메시지는 같지만 체인이 끊기므로 errors.Isfalse를 반환합니다. 호출자가 원인을 볼 수 있어야 한다면 %w를, 원인이 호출자가 의존하지 않았으면 하는 구현 세부 사항이라면 %v를 고르세요.

Go 1.20부터는 호출 하나로 여러 오류를 감쌀 수 있습니다: fmt.Errorf("%w; %w", err1, err2). 그러면 errors.Is는 둘 중 어느 것과도 일치합니다.

errors.Join: 여러 오류를 한 번에

검증과 정리 작업은 오류를 둘 이상 만들곤 합니다. errors.Join(Go 1.20)이 이를 묶어 줍니다.

출력:

name: required
email: required
age: negative
---
true
true

errors.Join은 모든 인자가 nil이면 nil을 반환하므로, 위 함수에는 "오류 없음"을 위한 특별 처리가 필요 없습니다.

Unwrap과 Is 메서드

errors.Iserrors.AsUnwrap 메서드를 호출해서 감싼 오류를 찾습니다. 원인을 담고 있는 커스텀 타입은 그것을 드러내야 합니다:

여러 오류를 감싸는 타입은 대신 Unwrap() []error를 구현합니다.

타입은 Is(target error) bool을 정의해서 동등성을 스스로 결정할 수도 있습니다. 예를 들어 상태 코드가 같은 모든 *HTTPError와 일치하게 하는 경우입니다. 필요한 일은 드뭅니다. 센티널을 감싸서 Unwrap으로 반환하면 대개 충분합니다.

타입 있는 nil 함정

오류 변수를 구체 타입으로 선언한 뒤 error로 반환하지 마세요:

func check() error {
	var err *ValidationError // nil pointer
	// ... no problem found
	return err // non-nil error! Its type is *ValidationError
}

타입 있는 nil 포인터를 담은 인터페이스는 nil이 아니므로 호출자의 if err != nil은 참이 됩니다. 성공했을 때는 리터럴 nil을 반환하고, 지역 오류 변수는 error 타입으로 두세요. 이유는 인터페이스 페이지에서 설명합니다.

어떤 종류를 고를까

호출자가 해야 할 일제공할 것
실패를 로그로 남기거나 보여 주기만 함fmt.Errorf("...: %w", err)
특정 조건 하나로 분기함센티널 var ErrX = errors.New(...)
실패의 세부 정보를 읽음필드가 있는 오류 타입
독립적인 여러 실패를 봄errors.Join

흔한 실수

  • 감싼 오류를 ==로 비교함. errors.Is를 쓰세요.
  • errors.As에 포인터가 아닌 것을 넘김. 대상 타입 변수의 포인터가 필요합니다.
  • 함수 안에서 "센티널"을 만듦. return errors.New("not found")는 호출할 때마다 새 값을 만들므로 호출자가 비교할 수 없습니다.
  • 모든 오류를 공개함. 공개한 센티널이나 타입은 하나하나가 API 약속입니다.
  • err.Error()로 매칭함. 문자열은 사람을 위한 것입니다.

자주 묻는 질문

Go에서 커스텀 오류는 어떻게 만드나요?

고정된 조건이라면 패키지 수준 센티널을 선언합니다: var ErrNotFound = errors.New("not found"). 데이터를 담는 오류라면 Error() string 메서드가 있는 타입을 정의합니다: type ValidationError struct { Field string }func (e *ValidationError) Error() string { return e.Field + " is invalid" }.

Go에서 오류는 어떻게 감싸나요?

%w 동사와 함께 fmt.Errorf를 씁니다: return fmt.Errorf("load user %d: %w", id, err). 새 오류의 메시지에 이전 메시지가 포함되고, errors.Unwrap, errors.Is, errors.As가 원래 오류에 닿을 수 있습니다. Go 1.20부터는 Errorf 호출 하나에 %w 동사를 여러 개 써서 여러 오류를 감쌀 수 있습니다.

errors.Is와 errors.As의 차이는 무엇인가요?

errors.Is(err, target)io.EOF 같은 센티널에 대해 "이 특정 오류 값이 체인 어딘가에 있는가"에 답합니다. errors.As(err, &target)는 "이 타입의 오류가 체인에 있는가"에 답하고, 있다면 target에 저장해서 필드를 읽을 수 있게 합니다.

errors.Join은 무엇을 하나요?

errors.Join(errs...)(Go 1.20)는 여러 오류를 하나로 합칩니다. 메시지는 개별 메시지를 줄바꿈으로 구분한 것이고, nil 오류는 버려지며, 모두 nil이면 nil을 반환합니다. 합쳐진 오류 중 하나라도 일치하면 errors.Iserrors.As가 일치합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기