[Go] 에러 정의와 핸들링

devlcw·2024년 12월 4일

개요

어떤 프로그래밍 언어든 에러를 핸들링하는 것은 중요하다. 오늘은 Go에서 에러의 정의와 핸들링하는 방법을 알아볼 것이다.

인터페이스

에러는 다음과 같은 인터페이스를 갖는다.

type error interface {
	Error() string
}

그러니까, 대충 커스텀 에러를 만들어서 Error만 구현해주면 된다는 것이다.

컨벤션

아마 Goland로 Go를 개발해봤을 시 IDE에서 에러 끝에 .을 붙이지 말라는 메세지를 만나본 적이 있을텐데, 이는 Go가 에러를 fragment로 치부하기 때문이다.

Go에서는 Rust에서의 Backtrace 같은 기능이 없다. 그래서 에러를 반환할 때 fmt.Errorf 메소드를 많이 사용한다. (물론 runtime.Stack을 호출할 순 있지만 완벽하진 않다.)

// 미리 정의된 에러
var errFooIsNotEven = errors.New("foo is not even")

func ValidateIsFooEven(foo int) error {
	if foo % 2 != 0 {
		return fmt.Errorf("validation failed: %w", errFooIsNotEven)
	}
    
    return nil
}

// validationo failed: foo is not even

또한, 불변성을 유지한답시고 다음처럼 같이 짜면 안된다.

const errFooIsNotEven = "foo is not even"

// ...
errors.New(errFooIsNotEven)

객체의 재생성 오버헤드나 이런 걸 떠나서, 저렇게 짜면 에러를 비교할 수 없게된다.
에러도 일종의 객체 취급이라 var로 전역에 선언하는걸 추천한다.

핸들링

이제 어떤식으로 에러를 핸들링하는지 알아보자.

Tuple

가장 단순하면서도 많이 사용되는 형태이다.
값이랑 같이 받고 err를 검증하는 형식이다.

func sumUntilTen(a int, b int) (int, error) {
	result := a + b
    if result > 10 {
        return 0, errors.New("invalid sum")
    }
    return 0, nil
}

// main.go
v, err := sumUntilTen(5, 6)
if err != nil {
	// 에러 핸들링
}

단일 에러

void 형태로 호출할 때 발생할 수 있는 에러를 반환할 때 많이 쓰인다.
혹은 Go로 웹 프레임워크를 많이 만져봤다면 단일 에러 반환을 많이 봤을 것이다.

var errFoo = errors.New("foo")
func fooMakesError() error {
	return errFoo
}

// main.go
err := fooMakesError()
if err != nil {
	// 에러 핸들링
}

defer로 에러 처리

반복적인 에러 처리(로깅 등등)가 싫다면 이런식으로 처리할 수도 있다.
물론, 가독성은 좋지 않다.

func fooHandlingError() {
	var err error
    defer func() {
    	if err != nil {
        	// 공통 에러 로깅
        }
    }()
    
    err = fooMakesError()
    if err != nil {
    	// 고유한 에러 핸들링, 혹은 비워둠
   		return
    }
}

비판

얼핏보면 Go의 에러 처리는 간결하고 좋아보일 수 있다. 그렇지만, 당연히 tradeoff를 감수한 것이기에 sucks 소리를 많이 듣는다.

Rust같은 경우는 ? 표현식을 언어단에서 지원하여, 가독성을 높였다. 반면 방금 소개한 Go의 에러 핸들링은 반복적이고, 코드가 길어지며, 에러의 디버깅이 어렵다. 그러니까, 자바로 예시를 들자면 Exception으로 싹 다 짬 때려버리는 것이다.

애초에 요새 트렌드랑 좀 다른 방법이다.

근래에 나온 언어들을 살펴보자. 모나딕한 에러 처리를 지원하는 걸 꽤 중요하게 여긴다. Kotlin, Rust, Swift, 심지어 자바마저 Optional을 지원한다.

물론 사용자가 직접 type wrapping을 해서 구현할 수 있겠지만, 이건 근본적인 해결책이 아니다. 프로젝트가 달라질 때마다 일일이 선언하거나 라이브러리를 가져오다니.. 구시대 C인가?

참조

profile
모든 스택에는 이유가 있다

0개의 댓글