📖 이 글은 Saturday Night 스터디에서 The Ultimate Go를 주제로 발표하기 위해 만들어졌습니다.
에러 핸들링은 무결성의 한 부분으로 아주 중요한 부분입니다. 에러를 핸들링하지 않으면 어떠한 문제가 발생했을 때 프로그램이 비정상 종료되어 안정성을 보장할 수 없습니다.
언어에서 제공하는 기본 에러 타입 구현을 살펴봅시다.
golang.org/pkg/builtin/#error
// Go SDK/src/builtin/builtin.go
package builtin
...
// The error built-in interface type is the conventional interface for error
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}
...
error 타입은 builtin 패키지에 정의되어 있습니다.
error
는 interface 타입이며, 문자열을 반환하는 Error()
메소드가 정의되어 있습니다.
우리가 에러를 처리할 때는 항상 error 인터페이스 타입을 사용하기 때문에 디커플링되어 있다고 할 수 있습니다.
Go에서 에러는 값이며, 인터페이스 디커플링을 통해 값을 평가하게 됩니다.
에러 처리를 디커플링하는 이유는 유지보수를 통해 애플리케이션의 코드가 계속해서 변경이 발생하게 되고 이러한 변경을 통해 광범위하게 영향을 발생시키기 때문입니다.
// Go SDK/src/errors/errors.go
package errors
func New(text string) error {
return &errorString{text}
}
type errorString struct {
s string
}
func (e *errorString) Error() string {
return e.s
}
errors 패키지에는 error 타입에 대한 기능들이 구현되어 있습니다.
에러 내용의 문자열 필드를 가지는 errorString
타입의 구조체가 있고 해당 구조체가 error 인터페이스의 Error()
메소드를 구현하고 있습니다.
New(text string)
생성자 함수를 통해 error 인터페이스 타입으로 반환하며 구체적인 타입은 외부로 노출시키지 않도록 구현되어 있습니다.
func (e *errorString) Error() string {
return e.s
}
Error()
메소드는 오류 상황에 대한 내용을 얻을 수 있어 에러에 대한 정보를 로깅할 때 사용합니다.
func main() {
if err := something(); err != nil {
fmt.Println(err)
return
}
...
}
func something() error {
return errors.New("it will occur error")
}
흔히 에러처리를 할 때 무조건 작성하게 되는 패턴의 코드입니다.
error 인터페이스 타입을 반환하는 something()
함수의 값을 err 변수에 할당하고 nil체크를 통해 에러가 발생했는지를 검사합니다.
representing an error condition, with the nil value representing no error.
builtin 패키지에서 error 인터페이스 타입에 작성되어 있는 주석내용 입니다.
nil 값은 error로 치지 않기 때문에 err != nil
코드가 성립하게 됩니다.
저는 실제로 실무에서 Go 프로젝트를 할 때 Uber Go Style Guide를 참고해서 코드를 작성하는 편인데 이 챕터에서도 이와 같은 내용을 다루고 있습니다.
코드를 작성할 때 에러 처리를 좀 더 명확하게 알 수 있도록 에러를 변수화하여 처리하는 방법을 알아보겠습니다.
package httpErrors
import (
"errors"
"fmt"
)
var (
ErrBadRequest = errors.New("bad request")
ErrPageMoved = errors.New("page moved")
)
일종의 컨벤션인데, 에러 변수의 첫 글자는 Err
로 시작합니다. 보통 에러들은 공통적으로 사용되는 경우가 많기 때문에 외부에서 접근이 가능하도록 대문자로 작성하는 것이 좋습니다.
위에 선언한 변수들은 에러에 대한 컨텍스트를 담게 되고 외부에서 해당 변수들을 타입으로써 검증을 진행하여 에러 처리를 할 수 있습니다.
import (
. "httpErrors"
)
func main() {
if err := something(); err != nil {
switch err {
case ErrBadRequest:
fmt.Println("Bad request occured")
return
case ErrPageMoved:
fmt.Println("The Page moved")
return
default:
fmt.Println(err)
return
}
}
}