Kim Seogyu
Backend

Go 에러 핸들링 전략 실무 가이드

Go에서 errors.Is/As, 래핑, 도메인 에러를 실무적으로 적용하는 기준을 정리합니다.

Published 2026년 1월 2일2 min read351 words

Go 에러 핸들링 전략 실무 가이드

핵심 원칙

  • 에러는 문자열이 아니라 의미 있는 값으로 다룹니다.
  • 상위 계층으로 올릴 때는 %w원인 체인을 유지합니다.
  • 분기 판단은 문자열 비교가 아니라 errors.Is, errors.As를 사용합니다.

1) 래핑: %w를 기본으로

func readConfig(path string) error {
	if _, err := os.ReadFile(path); err != nil {
		return fmt.Errorf("config read failed: %w", err)
	}
	return nil
}

%v를 쓰면 원인 에러를 잃어버립니다. 에러 전달 경로에서는 %w를 기본으로 두세요.

2) errors.Is로 분기

var ErrNotFound = errors.New("not found")

func findUser(id string) (*User, error) {
	u, err := repo.Find(id)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("find user: %w", err)
	}
	return u, nil
}

호출부는 체인 전체에서 의미를 찾습니다.

if errors.Is(err, ErrNotFound) {
	// 404
}

3) errors.As로 타입 추출

type ValidationError struct {
	Field string
	Msg   string
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("invalid %s: %s", e.Field, e.Msg)
}
var vErr *ValidationError
if errors.As(err, &vErr) {
	log.Printf("field=%s msg=%s", vErr.Field, vErr.Msg)
}

4) 도메인 에러로 HTTP 매핑

type Code string

const (
	CodeNotFound Code = "NOT_FOUND"
	CodeConflict Code = "CONFLICT"
)

type DomainError struct {
	Code  Code
	Msg   string
	Cause error
}

func (e *DomainError) Error() string { return e.Msg }
func (e *DomainError) Unwrap() error { return e.Cause }

핸들러에서는 도메인 코드만 보고 응답을 결정합니다.

func toStatus(code Code) int {
	switch code {
	case CodeNotFound:
		return http.StatusNotFound
	case CodeConflict:
		return http.StatusConflict
	default:
		return http.StatusInternalServerError
	}
}

운영에서 중요한 규칙

  • 에러 메시지는 사용자 메시지와 내부 로그 메시지를 분리합니다.
  • 재시도 여부(temporary/permanent)를 코드 또는 타입으로 표현합니다.
  • 로그는 "한 번만" 책임 계층에서 남깁니다. (중복 로깅 금지)

피해야 할 패턴

  • if err != nil { return errors.New("...") }로 원인 손실
  • err.Error() 문자열 포함 여부로 분기
  • 패키지 전역 센티넬 에러를 과도하게 남발

요약

Go 에러 전략의 핵심은 단순합니다.

  • 래핑은 %w
  • 분기는 Is/As
  • 외부 계약은 도메인 에러 코드

이 세 가지를 팀 규칙으로 고정하면 장애 대응 속도와 코드 일관성이 크게 좋아집니다.

Share

Related Articles

Comments

이 블로그는 제가 알고 있는 것들을 잊지 않기 위해 기록하는 공간입니다.
직접 작성한 글도 있고, AI의 도움을 받아 정리한 글도 있습니다.
정확하지 않은 내용이 있을 수 있으니 참고용으로 봐주세요.

© 2026 Seogyu Kim