はじめに
goをさわって数ヶ月ですが、雰囲気では書けていたものの
errorやエラーハンドリングについてはもやもやしたままだったので自分理解メモの③
関連
この記事の関連です。
アジェンダ
- エラーハンドリングで困る事
- pkg/errorsでのエラーハンドリング(errors.Wrap)
- pkg/errorsでのエラーハンドリング(errors.Cause)
1. エラーハンドリングで困る事
main -> packageA.method -> packageB.method のように呼び出しを行っている場合、
packageB.methodで起きたエラーをpackageA.methodで拾ってmainにさらに返したいときがあります。
packageA.methodでのハンドリングによっては、
mainでなんのエラーだったかわかりにくくなるときがあります。
サンプル
ここでは main -> fuga.method -> hoge.method のように呼び出しているとします。
hoge.methodが返すエラーをfuga.methodで
ハンドリングしてさらにエラーを返します。
mainではerrrorかどうかをハンドリングします。
コード
package hoge import "fmt" type HogeSomethingError struct{} func (f *HogeSomethingError) Error() string { return fmt.Sprintf("this is HogeSomethingError") } type HogeAnythingError struct{} func (f *HogeAnythingError) Error() string { return fmt.Sprintf("this is HogeAnythingError") } func DoSomething() error { return &HogeSomethingError{} } func DoAnything() error { return &HogeAnythingError{} } func DoExciting(b bool) error { if b { return DoSomething() } else { return DoAnything() } return nil }
package fuga import ( "errors" "go-error-handling_pkg-errors/package/hoge" ) func DoExciting(b bool) error { err := hoge.DoExciting(b) if err != nil { switch err.(type) { case *hoge.HogeSomethingError: return errors.New("error1 at fuga") case *hoge.HogeAnythingError: return errors.New("error2 at fuga") } } return nil }
package main import ( "fmt" "os" "go-error-handling_pkg-errors/package/fuga" ) func main() { if err := fuga.DoExciting(false); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Println("main success") }
出力
$ go run src/go-error-handling_pkg-errors/sample01/main.go error2 at fuga exit status 1
困ること
mainでは、error2 at fuga と出力され、
エラーがあったことはわかるものの、hogeでエラーだったことがわかりません。
2. pkg/errorsでのエラーハンドリング(errors.Wrap)
そんな場合に pkg/errors でエラーハンドリングを行ってみます。
インストール
使う前にインストールしておきます。
pkg/errrosは go getなら以下のようにしてインストールできます。
go get -v github.com/pkg/errors
自分はglide環境なので、glide.yamlを以下のようにしてglide installしました。
package: go-error-handling_pkg-errors import: - package: github.com/pkg/errors
サンプル
さきほどの fuga.method をpiyo.methodに置き換えて、
main -> piyo.method -> hoge.method と呼び出すようにします。
また、piyo.methodでは`pkg/errorsを使ってエラーハンドリングするようにしてみます。
コード
上記と同じです
package piyo import ( "go-error-handling_pkg-errors/package/hoge" "github.com/pkg/errors" ) func DoExciting(b bool) error { err := hoge.DoExciting(b) if err != nil { switch err.(type) { case *hoge.HogeSomethingError: return errors.Wrap(err, "error1 at piyo") case *hoge.HogeAnythingError: return errors.Wrap(err, "error2 at piyo") } } return nil }
package main import ( "fmt" "os" "go-error-handling_pkg-errors/package/piyo" ) func main() { if err := piyo.DoExciting(false); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } fmt.Println("main success") }
出力
$ go run src/go-error-handling_pkg-errors/sample02/main.go error2 at piyo: this is HogeAnythingError exit status 1
結果
error2 at piyoに続いて、大元の: this is HogeAnythingErrorも表示されました。
3. pkg/errorsでのエラーハンドリング(errors.Cause)
errors.Causeというのもあり、
これを使うと上記の例でいうとmainで原因となるエラーの型ハンドリングが可能となります。
mainだけ変更した例をのせておきます。
コード
package main import ( "fmt" "os" "github.com/pkg/errors" "go-error-handling_pkg-errors/package/hoge" "go-error-handling_pkg-errors/package/piyo" ) func main() { if err := piyo.DoExciting(false); err != nil { switch errors.Cause(err).(type) { case *hoge.HogeSomethingError: fmt.Fprintln(os.Stderr, "case1 at main:", err) case *hoge.HogeAnythingError: fmt.Fprintln(os.Stderr, "case1 at main:", err) } } fmt.Println("main success") }
出力
$ go run src/go-error-handling_pkg-errors/sample03/main.go case1 at main: error2 at piyo: this is HogeAnythingError main success
おわり
こちらの参考でも言われてますが、
たしかにあまり深くWarpしまくるのも、という気もするので結局悩ましいループになりそう\(^o^)/