1 of 7

wrapmsg

— fmt.Errorf のメッセージに制約を —

👉 github.com/Warashi/wrapmsg

golang.tokyo #32

澤田 晋之介

2 of 7

自己紹介

  • 2018年にCAに新卒入社
  • Amebaのサーバーサイドエンジニア
  • 最近やってること
    • 既存OSSの再実装(勉強)
      • lemonade (Warashi/muscat)
      • mosh
    • Writing A Compiler In Go(勉強)
    • バックエンドシステムの刷新(仕事)

3 of 7

こんなことありませんか?

func Hoge() error {

if err := Fuga(); err != nil {

return fmt.Errorf(“failed to Fuga: %w”, err)

}

}

func main() {

if err := Hoge(); err != nil {

log.Println(err)

}

}

// OUTPUT: failed to Fuga: failed XXX: cannot XXX: failed to……

4 of 7

「失敗した」が冗長

  1. “failed” も “cannot” もつまり失敗したことを言っている
  2. wrapする度に重なるのでメッセージがやたら伸びる
  3. error型なんだから失敗したことは型からわかる

重要なのは「何に」失敗したのか

5 of 7

じゃあどういったメッセージがいいのよ

— wrapmsg が生まれるまで —

こういうときは(準)標準パッケージを調べる

👉 github.com/golang/pkgsite にて呼び出しの記述をそのままメッセージに詰めているものがあった� ex) fmt.Errorf("git.Init: %w", err)

👉 アプリケーションを書くならこれでよくね?(ライブラリを書くときには適さないかも)

👉 コードレビューでtypo指摘するの辛いからlinterにしよ

6 of 7

wrapmsgがやること

  1. fmt.Errorf を見つける
  2. フォーマット文字列に `%w` が含まれるならlintの対象にする
  3. 引数に渡されたerrorを返した呼び出しを見つける (SSA)
  4. SSAで見つけた呼び出しからtoken.Posを参考にAST表現を見つける
  5. AST表現を文字列に書き出す
  6. フォーマット文字列と違っていたらlint errorとする

最終的に強制するのは “foo.bar.Baz: %w” という形式

7 of 7

謝辞

github.com/gostaticanalysis/skeleton/v2 のおかげで開発が楽でした。ありがとうございます。

また、(準)標準パッケージを調査してくれた同僚には感謝しています。

最後に、こんな日曜大工みたいなlinterをプロジェクトに突っ込んでも笑って見守ってくれたチームのみんなにも感謝しています。