golang - error

아래 글은 golang을 공부할 목적으로 웹에서 본 글들을 정리한 것이다.

Error

스택 상태 출력과 에러 핸들링

github.com/pkg/errors 를 사용하여 error 오브젝트를 랩핑하면 스택 트레이스를 추가할 수 있다.
error 중의 스택 트레이스는 “%+v” 포맷 지정으로 출력 할 수 있다.
또 errors.Cause를 사용하여 에러 원인을 뽑아 낼 수 있으므로 이것을 이용하여 함수 호출 측에서 에러 원인에 따른 분기를 할 수 있다.

에러 던지는 측

package piyo

import (
    "github.com/pkg/errors"
    "strings"
)

var (
    // 에러 정의
    NotFound = errors.New("not found")
    UnexpectedID = errors.New("unexpected id")
)

func Find(id string) ([]Piyo, error) {
    n, err := strings.Atoi(id)
    if err != nil {
        return nil, errors.WithStack(UnexpectedID)
    }
    p, err := fetch(n)
    if err != nil {
        return nil, errors.WithStack(NotFound)
    }
    return p, nil
}

에러를 받는 측

package main

import (
    "fmt"
    "os"
    "github.com/kaizoa/piyo"
    "github.com/pkg/errors"
)

func main() {
    piyos, err := piyo.Find("foo")
    // 에러에 따라서 메시지를 바꾼다
    if err != nil {
        switch errors.Cause(err) {
        case piyo.NotFound:
            fmt.Fprintln(os.Stderr, "Piyo는 발견하지 못함")
        case piyo.UnexpectedID:
            fmt.Fprintln(os.Stderr, "Piyo의 ID가 틀리다")
        default:
            fmt.Fprintln(os.Stderr, "알 수 없는 에러 발생")
        }
        panic(err)
    }
    for _, p := range piyos {
        fmt.Printf("%+v", p)
    }
}

자체 구조체를 오류로 돌려주고 싶다

아래처럼 곤란한 경우 자체적으로 에러 형을 정의헤서 돌려주면 좋다.

  • func DoSomething() (result String, err error, myError MyError, myError2 MyError2) 처럼 복수의 에러를 돌려주는 코드로 되어 인터페이스가 복잡
  • errors.New 에서 만든 에러는 클라이언트 코드의 미세한 에러 처리가 번거롭다…

자체 에러 형을 만드는 것의 포인트

  1. error 인터페이스를 구현한 구조체를 만든다
  2. 클라이언트 코드는 switch에서 에러 처리를 분기

Go에서는 error 인터페이스가 있으며 이를 구현한 타입이라면 어떤 것이든 error 로서 되돌려줄 수 있다.
복수의 실수형을 갚고 싶은 경우 모두 에러 형으로 이를 구현해 두면, 함수의 인터페이스를 func DoSomething() (result String, err error)로 간단하게 유지할 수 있다. 한편 클라이언트 코드는 switch 에서 각 오류 형별 처리를 할 수 있다. 그래서 함수를 제공하는 측, 클라이언트측 어느쪽도 구현이 간단하다.

예) HTTP 응답 상태 코드에 따라 ClientError와 ServerError을 반환

package main

import (
    "net/http"
    "io/ioutil"
    "fmt"
)

func main() {
    body, err := httpGet("http://www.mocky.io/v2/53dc17ab6ab0013e054ebdd8")

    if err != nil {
        switch err := err.(type) {
        case ClientError: fmt.Printf("client error: status code is %d and message is \"%s\"", err.Code, err.Message)
        case ServerError: fmt.Printf("server error: status code is %d", err.Code)
        default: fmt.Printf("other error: %s", err)
        }
    }

    println(string(body))
}

func httpGet(url string) (body []byte, err error) {
    resp, err := http.Get(url)

    if err != nil {
        return body, err
    }

    if 400 <= resp.StatusCode && resp.StatusCode <= 499 {
        message, _ := ioutil.ReadAll(resp.Body)
        return body, ClientError{resp.StatusCode, string(message)}
    }

    if 500 <= resp.StatusCode && resp.StatusCode <= 599 {
        return body, ServerError{resp.StatusCode}
    }

    body, err = ioutil.ReadAll(resp.Body)

    if err != nil {
        return body, err
    }

    return body, err
}

type ClientError struct {
    Code int
    Message string
}

func (err ClientError) Error() string {
    return fmt.Sprintf("Status code: %d, message: %s", err.Code, err.Message)
}

type ServerError struct {
    Code int
}

func (err ServerError) Error() string {
    return fmt.Sprintf("Status code: %d", err.Code)
}

이 글은 2018-11-08에 작성되었습니다.