LoginSignup
15
10

More than 5 years have passed since last update.

xerrors - エラー設計の注意点

Last updated at Posted at 2019-02-18

トレースの情報を上位のレイヤーに渡すだけならパッケージで定義したエラーの型は公開しない

トレースの情報だけをラップして渡していきたい場合には、Formatを実装していればパッケージ内で独自に定義したエラータイプは非公開にして問題ありません。非公開の場合は、Isでマッチなどはすることはありません。しかし Unwrap を実装しないと、より下位にラップされた公開されているエラーに到達できなくなってしまうので注意が必要です。

package pkg1

import (
    "fmt"

    "golang.org/x/xerrors"
)

// 非公開の型
type pkg1Error struct {
    msg   string
    err   error
    frame xerrors.Frame
}

func (e *pkg1Error) Error() string {
    return e.msg
}

// Unwrapメソッドは実装する
func (e *pkg1Error) Unwrap() error {
    return e.err
}

func (e *pkg1Error) Format(s fmt.State, v rune) { xerrors.FormatError(e, s, v) }

func (e *pkg1Error) FormatError(p xerrors.Printer) (next error) {
    p.Print(e.Error())
    e.frame.Format(p)
    return e.err
}

func UserSearch(uID string) (string, error) {
    pkgErr := &pkg1Error{
        msg:   "hello",
        err:   nil,
        frame: xerrors.Caller(1),
    }
    return "", xerrors.Errorf("error user %v not found: %w", uID, pkgErr)
}
func main() {
    _, err := pkg1.UserSearch("12345")
    fmt.Printf("%+v\n", err)
}

正しく外部パッケージで定義されたエラーのトレース情報が出力されています。

error user 12345 not found:
    xerrors/pkg1.UserSearch
        /Users/sonatard/tmp/xerrors/pkg1/user.go:37
  - hello:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:9

逆にIsやAsを利用したい場合には、型を公開しなければなりません。

自分で定義した型を他のパッケージに公開したいが、IsやAsでマッチさせたくない場合はUnwrap型を実装しない

Unwrap を実装しないことで公開されているエラーの型をIsやAsでマッチさせなくすることができます。
またFormatを実装することで、Unwrap を実装しなくてもエラーのトレースは出力することはできます。

これは標準ライブラリで返ってきた型をラップしたいが、外部パッケージに公開はしたくないときに使います。

package pkg1

import (
    "fmt"
    "io"

    "golang.org/x/xerrors"
)

type Pkg1Error struct {
    msg   string
    err   error
    frame xerrors.Frame
}

func (e *Pkg1Error) Error() string {
    return e.msg
}

func (e *Pkg1Error) Format(s fmt.State, v rune) { xerrors.FormatError(e, s, v) }

func (e *Pkg1Error) FormatError(p xerrors.Printer) (next error) {
    p.Print(e.Error())
    e.frame.Format(p)
    return e.err
}

func UserSearch(uID string) (string, error) {
    pkgErr := &Pkg1Error{
        msg:   "hello",
        err:   xerrors.Errorf("error: %w", io.ErrUnexpectedEOF), // 標準ライブラリからエラーが返ってきた場合を想定
        frame: xerrors.Caller(1),
    }

    return "", xerrors.Errorf("error user %v not found: %w", uID, pkgErr)
}
func (e *Pkg1Error) Unwrap() error {
    return e.err
}
func main() {
    _, err := pkg1.UserSearch("12345")
    fmt.Printf("%v\n", xerrors.Is(err, io.ErrUnexpectedEOF))
    fmt.Printf("%+v\n", err)
}
  • Unwrapの実装をしている場合の結果

Unwrap を実装しているため Istrue となる。

true
error user 12345 not found:
    xerrors/pkg1.UserSearch
        /Users/sonatard/tmp/xerrors/pkg1/user.go:35
  - hello:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:31
  - error:
    xerrors/pkg1.UserSearch
        /Users/sonatard/tmp/xerrors/pkg1/user.go:31
  - unexpected EOF
  • Unwrap を実装していない場合の結果

Unwrap を実装していないため Isfalse となる。

Unwrap を実装していなくとも同じようにエラーのトレースは表示される。

false
error user 12345 not found:
    xerrors/pkg1.UserSearch
        /Users/sonatard/tmp/xerrors/pkg1/user.go:35
  - hello:
    main.main
        /Users/sonatard/tmp/xerrors/main.go:31
  - error:
    xerrors/pkg1.UserSearch
        /Users/sonatard/tmp/xerrors/pkg1/user.go:31
  - unexpected EOF

関連情報

15
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
10