Go의 에러처리

  • go의 Error는 Error()를 들고있는 인터페이스다.
  • Exception처리가 없어서 매우 짜증난다.

에러 처리의 종류

Sentinel Error

1
if err == ErrSomething { ... }
  • 에러의 종류에 따라 Control Flow가 바뀜
  • 안좋다. 패키지간 의존성이 생긴다. 쓰지말자.

Error Type

1
2
3
4
5
6
7
8
9
type MyError struct {
        Msg string
        File string
        Line int
}
func (e *MyError) Error() string { 
        return fmt.Sprintf("%s:%d: %s”, e.File, e.Line, e.Msg)
}
return &MyError{"Something happened", “server.go", 42}
  • 에러에 추가정보를 넣기 위해서 타입을 만드는 경우
  • 에러 타입을 정의한 패키지와 모든 패키지가 큰 의존성을 갖게 됨.
  • 사용을 피하자.

Opaque Error

  • 에러의 내용을 검사하지 말고 그저 리턴하기
  • 만약 외부 동작에 의해서, 에러의 내용을 검사하는게 불가피하다면,
    • type assertion을 이용해서 interface로 변환가능한지 살펴보기
    • 즉 Error에 대해서 어떤 행위가 가능한지 살펴보기.
    • 이 방식으로 구현하면 error를 정의한 패키지를 임포트 할 필요가 없다.
  • 결론은 이렇게 단순하게 에러만 리턴하자.

더 나은 에러 처리를 위한 조언들

Error에 내용을 덧붙이기

  • fmt 패키지의 Errorf를 쓰면 error를 포매팅해서 덧붙일 수 있다.
  • 근데 return fmt.Errorf("decompress %v: %v", name, err) 이렇게 쓰면 원래의 err 타입이 날아가버린다.
    • Wrapping 한 에러가 ErrNotFound 였더라도 비교해서 검사할 수 없다. (불가피한 경우에도)
  • 그래서 %w로 포매팅하자.
    • 그러면 if errors.Is(err, sql.ErrNoRows) 로 검사할 수 있다.

에러의 종류 검사하기

  • 예전에는 TypeAssertion을 썼지만, 이건 타입을 %v로 뭉개버렸으면 소용없다.
  • 1.13부터는 errors.As를 쓰면 손쉽다. 여러번 Wrap되어있어도 다 까본다고 한다.

강력한 pkg/errors

  • 외부 pkg인 errors를 쓰면 그냥 스택까지 쫙 찍어준다.
  • 요걸 써보길 추천. errors가 리턴해주는 에러도 여전히 에러기 때문에 Is나 As랑도 잘 맞는다.