はじめに
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^)/