Goの静的解析の事始めとしてエラーラップフォーマットのLinterをつくった
こちらのもくもく会も活用して、簡単なLinterをつくりました。
つくったもの
Goの静的解析の事始めとして、エラーのラップフォーマットの間違いを検知するLinter errwrapfmtをつくりました。
errwrapfmtは文字列リテラルに %w があったらエラーをラップしようとしていると判断して : %w
(コロン半角スペースが前方にあるか)というフォーマットになっているかどうかチェックするLinterです。文字列リテラル(正確にはBasicLit)に対してシンプルに正規表現をかけてます。
このLinterをつくろうと思ったきっかけは、xerrorsによるエラーのラップでこのフォーマットを間違うと、スタックトレースが一部冗長になるという問題を知ったからです。
Goではエラーを扱う標準パッケージのerrorsはGo 1.18現在スタックトレースに対応していません。スタックトレースを出力したければpkg/errorsやxerrorsを使う必要があります。
xerrors.Errorf()はdeprecatedになりfmt.Errorf()を使うようにgodocに書かれていますが、fmt.Errorf()を使えばそこでスタックトレースは途切れます。スタックトレースを出力したい場合依然としてxerrors.Errorf()を利用することになると思っています。xerrors.Errorf()は : %w
でないとラップされたエラーがスタックトレース上で冗長に表示されます。そしてこれはコンパイラでも検知されないため、事前に検知するには静的解析が有効です。
※スタックトレース周りの話は Goでスタックトレースを出力するには - あぼぼーぼ・ぼーぼぼ にもうちょい詳しく書きました。
参考にしたもの
今回はgo/analysisを活用したのでだいぶ考えることが減りました。つくりたいLinterのロジックに集中できましたし、(独自の記法ではあるけど)analysistestでかなり簡単にテスト駆動開発ができるのが良かったです。
たくさんの参考情報をもとに理解を進めつつ、今回つくりたいものに必要最低限な知識は獲得できたかなと思います。具体的にはプログラミング言語Go完全入門 14.静的解析とコード生成がめちゃめちゃ役立ちました。
あとはAST周りだとGoの言語仕様を途中まで読んでいたので、EBNFを理解してNotationが読めるようになっていたのも大きかったです。やっぱり言語仕様に戻ってこれるのは大事です。それからGoAst ViewerでASTの可視化をしてみたりも。
書籍だと『エキスパートたちのGo言語』の第1章に循環複雑度を計測するCLIツールをつくるところがあり参考になりました。今回あえてgostaticanalysis/skeletonを使わずにつくってみましたが、結果的に似たような構成になりました。というのも書籍のなかで実装方法の参考にされていたloopclosure.go(などgo/analysis/passesにある実装)がそういう実装になっているからです。また業務でもお世話になっているstaticcheckなどのhttps://github.com/dominikh/go-toolのコードも参考にしました。
静的解析楽しいぞい
errwrapfmt自体はあとgolangci-lint対応くらいしかやりたいことがないのですが、このくらい簡単なものを作ってみて「静的解析は結構楽しいぞ」と思えたので、またなにか作りながら学習したいですね。