817 Views
June 07, 24
スライド概要
弊社のサービス開発において、 Golang の静的解析を導入し始めたので、簡単に静的解析の実装方法を説明します。
Golang の場合は、環境が整っているので誰でも簡単に導入できます。ぜひ試してみてください。
Golang の静的解析に入門する @okarin 1
Golang の静的解析に入門する 自己紹介 岡部 京太 @okarin 所属 ● 株式会社ハイヤールー ○ エンジニア募集中!! エンジニアリング領域 ● バックエンド ● 認証・認可 SNS等 ● X: @okarin_dev ● GitHub: @ksrnnb 好きなもの ● ブロッコリー ● ランニング Hireroo Tech Blog Go Conference 2024 当日スタッフとして参加!! 2
Golang の静的解析に入門する なぜ静的解析が必要? ● 同じレビューコメントをしたくない ● 人間がチェックすると見逃す可能性がある 静的解析で機械的にコードの品質を担保! 3
Golang の静的解析に入門する 静的解析ツール作成の流れ ● 対象を決める ○ 今回は Accept interfaces, return structs を遵守するツール ● skeleton を使って雛形コードの生成 ● Analyzer の実装 ● unitchecker をつかって実行する 詳細は Tech Blog を参照 4
Golang の静的解析に入門する skeleton を使って雛形コードの生成 notreturninterface.go $ skeleton github.com/xxxxxxxxx/notreturninterface package notreturninterface import ( "go/ast" ) skeleton を使うと 雛形のコードが自動で生成される 基本的に run 関数を実装するだけで良い "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/analysis/passes/inspect" "golang.org/x/tools/go/ast/inspector" const doc = "notreturninterface is ..." // Analyzer is ... var Analyzer = &analysis.Analyzer{ Name: "notreturninterface", Doc: doc, Run: run, // Analyzer.Run の実行前に Requires の Analyzer が実行される Requires: []*analysis.Analyzer{ inspect.Analyzer, }, } func run(pass *analysis.Pass) (any, error) { // ... } 5
Golang の静的解析に入門する Analyzer の実装 - 1 func run(pass *analysis.Pass) (any, error) { // inspect.Analyzer の結果を利用する inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) // AST を走査するときにフィルターする Node nodeFilter := []ast.Node{ (*ast.FuncDecl)(nil), } // 深さ優先探索の Preorder 順で走査 inspect.Preorder(nodeFilter, func(n ast.Node) { switch n := n.(type) { // 関数定義の場合 case *ast.FuncDecl: // 戻り値があるかどうかをチェック if n.Type.Results == nil || n.Type.Results.List == nil { return } // ... *inspector.Inspector をつかうと AST を走査して型情報などを調べることができる 6
Golang の静的解析に入門する
Analyzer の実装 - 2
inspect.Preorder(nodeFilter, func(n ast.Node) {
switch n := n.(type) {
case *ast.FuncDecl:
// ...
// 戻り値ごとにループ
for _, field := range n.Type.Results.List {
// 型情報の取得
typeExpr := pass.TypesInfo.TypeOf(field.Type)
if typeExpr == nil {
continue
}
// interface かどうかのチェック
if _, ok := typeExpr.Underlying().(*types.Interface); !ok {
continue
}
// TODO: Add allow list and ignore flag
pass.Reportf(n.Pos(), "function %s must not return interface %s, but struct",
n.Name.Name, typeExpr)
}
// ...
戻り値として interface を返している場合はレポートする
7
Golang の静的解析に入門する unitchecker をつかって実行する cmd/notreturninterface/main.go $ go build ./cmd/notreturninterface package main $ go vet -vettool="$(pwd)/notreturninterface" \ -notreturninterface.ignore=SomeInterface \ ./... import ( "github.com/ksrnnb/notreturninterface" ) フラグを使う場合 `${Analyzer.Name}.${flagName}` で指定 unitchecker は go vet 経由でしか実行できないので注意 (multichecker を使えば直接実行できる) "golang.org/x/tools/go/analysis/unitchecker" func main() { unitchecker.Main( notreturninterface.Analyzer, ) } 8
Golang の静的解析に入門する まとめ ● 静的解析を使うと同じレビューコメントをしなくて済む ● 静的解析ツールの自作は意外と簡単! (静的解析の良い学習教材があれば教えてください!) 9