pkg/errorsのerrorを独自定義のerrorに使ってしまうと微妙という話
何も考えずにgoimportsで補完された結果のコードを残しておいたら、pkg/errorsのerrorが独自定義のerrorに使われてしまって微妙だった。具体的に言うと意図しないスタックトレースを表示してしまうことになる。
例えば以下の様なコードのとき。
package main import ( "log" "github.com/pkg/errors" ) // ErrNotFound : var ErrNotFound = errors.New("not found") func main() { if err := run(); err != nil { log.Fatalf("%+v", err) } } func run() error { return ErrNotFound }
実行してみると以下の様になる。
2018/09/20 07:26:27 not found main.init VENV/go/sandbox/example_error/00/main.go:10 runtime.main /usr/lib/go/src/runtime/proc.go:186 runtime.goexit /usr/lib/go/src/runtime/asm_amd64.s:2361 exit status 1
おそらく今回で言えば run()
の位置を表示することを期待していた。それがmain.initという謎の位置(実際は謎ではないのだけれど)が表示されてしまって、傍目から見てどこで発生したエラーかが分からずという感じになる。なんでダメかと言うと、pkg/errorsのerrorは生成したタイミングのstack frameを記録しておくという実装なため(内部的にはruntimeパッケージのCallers()
が呼ばれている)。
runtime.Callers
ちょっとだけ runtime.Callers()
を直接使ってみて、stack frameの位置を表示してみる。
package main import ( "fmt" "runtime" ) func main() { f() } func f() { g() } func g() { const depth = 32 var pcs [depth]uintptr n := runtime.Callers(1, pcs[:]) for _, pc := range pcs[0:n] { fn := runtime.FuncForPC(uintptr(pc)) if fn == nil { fmt.Println("<unknown>") continue } file, lineno := fn.FileLine(uintptr(pc)) fmt.Println(file, ":", lineno) } }
pythonなどでcurrent frameを取ったりするのとまぁおんなじような感じ。
VENV/go/sandbox/example_error/02/main.go : 19 VENV/go/sandbox/example_error/02/main.go : 14 VENV/go/sandbox/example_error/02/main.go : 10 /usr/lib/go/src/runtime/proc.go : 207 /usr/lib/go/src/runtime/asm_amd64.s : 2362
ここまで来ると何がダメだったのかは明らか。このCallers()
が呼ばれた位置を記録するため。実行時(error生成時)の位置が表示されていた。
おそらくやりたかったこと
標準ライブラリのerrors方のNewのつもりで使っていた。それが誤ってpkg/errorsの方のNew
が呼ばれてしまって。。という感じ。goimportsによるimport文の補完結果をユニークにするなら、fmtの方を使えば良かったかもしれない。こういう感じ。
package main import ( "fmt" "log" "github.com/pkg/errors" ) // ErrNotFound : var ErrNotFound = fmt.Errorf("not found") func main() { if err := run(); err != nil { log.Fatalf("%+v", err) } } func run() error { return errors.Wrap(ErrNotFound, "on run") }
今度は期待通り。
2018/09/20 07:36:33 not found on run main.run VENV/go/sandbox/example_error/01/main.go:20 main.main VENV/go/sandbox/example_error/01/main.go:14 runtime.main /usr/lib/go/src/runtime/proc.go:198 runtime.goexit /usr/lib/go/src/runtime/asm_amd64.s:2361 exit status 1