Slide 1

Slide 1 text

The Go gopher was designed by Renée French. The gopher stickers was made by Takuya Ueda. Licensed under the Creative Commons 3.0 Attributions license. 静的解析を使った リファクタリングのススメ 2022年02月05日(土) 資料:https://tenn.in/refactor

Slide 2

Slide 2 text

上田拓也 Go ビギナーズ
 Go Conference
 @tenntenn tenntenn.dev Google Developer Expert (Go) 一般社団法人 Gophers Japan 代表理事 Experts Team

Slide 3

Slide 3 text

リファクタリング ■ 外部から見たプログラムの挙動を変えずに改善する ● 可読性を上げる ● パフォーマンスを改善する ● 設計を見直す

Slide 4

Slide 4 text

静的解析ツール ■ プログラムを実行せずに解析するツール ● 公式ではgo vetがある コーディング 010100 101000 101000 コンパイル 010100 101000 101000 デプロイ リリース テスト QA 監視 静的解析

Slide 5

Slide 5 text

静的解析とリファクタリング ■ 静的解析によってリファクタリングをサポート ● コードの書き換え ○ プログラムの意味を変えずに安全に変更する ○ 文字列置換では実現が難しい ● 静的解析ツールで見つける ○ go/analysisパッケージを使ったツール ○ golangci-lintなどのサードパーティ製ツール ○ バグとは言えないが可読性が低いもの ● ソースコードの定量的な情報を分析する ○ 関数の長さ、循環複雑度

Slide 6

Slide 6 text

goパッケージ go/ast 抽象構文木(AST)を提供 go/build パッケージに関する情報を集める go/constant 定数に関する型を提供 go/doc ドキュメントをASTから取り出す go/format コードフォーマッタ機能を提供 go/importer コンパイラに適したImporterを提供 go/parser 構文解析 機能を提供 go/printer AST 表示機能を提供 go/scanner 字句解析 機能を提供 go/token トークンに関する型を提供 go/types 型チェックに関する機能を提供 6

Slide 7

Slide 7 text

x/tools/goパッケージ analysis 静的解析ツールをモジュール化するパッケージ ast AST関連のユーティリティ callgraph call graph関連 cfg control flow graph関連 expect 構造化されたコメントを処理する packages Go Modulesを前提としたパッケージ情報の収集から構文解析、 型チェックまでを行うパッケージ pointer ポインタ解析 ssa Static Single Assignment (SSA) 関連 types 型情報関連 7

Slide 8

Slide 8 text

静的解析のフェーズ ■ 静的解析はいくつかのフェーズに分かれている ● 後のフェーズにいくこと、さらに詳しい情報が手に入る ● 各フェーズで手に入る情報を使い分けながら解析する ● 各フェーズで手に入る情報の紐付けの仕方がキモ ○ このノードの型情報は?など 8 構文解析 型チェック 静的単一代入形式 ポインタ解析

Slide 9

Slide 9 text

コードの修正 ■ gofmtを使った修正 ● 例:interface{} -> any ○ gofmt -w -r 'interface{} -> any' ○ https://go-review.googlesource.com/c/go/+/368254 ● ただの文字列置換 ■ 標準&準標準の静的解析ツールによる修正 ● https://pkg.go.dev/golang.org/x/tools/cmd ● go fix:言語仕様の変更などに追従(Go1.0以前に活躍) ● eg:exampleベースのリファクタリングツール ● gorename:名前を変更するツール ● gomvpkg:パッケージを移動させるツール ■ その他 ● rsc/rf:謎のrsc作のリファクタリングツール

Slide 10

Slide 10 text

go/analysisを使った修正 ■ analysis.SuggestedFix構造体を使う ● analysis.Diagnostic構造体のフィールドとして設定する ● -fixオプションで提案を反映することができる ○ singlecheckerとmulticheckerの場合のみ ● goplsからも使用できる ● 例:https://github.com/gostaticanalysis/nakedreturn 10 type SuggestedFix struct { Message string // 変更の概要 TextEdits []TextEdit }

Slide 11

Slide 11 text

リファクタリングポイントを見つける ■ 静的解析ツールで見つける ● https://github.com/gostaticanalysis/nakedreturn ● https://github.com/gostaticanalysis/dive ● https://github.com/gostaticanalysis/zero ● https://github.com/gostaticanalysis/noctor ● https://github.com/gostaticanalysis/debugcode ● https://github.com/gostaticanalysis/elseless ● https://github.com/gostaticanalysis/innertypealias ● https://github.com/gostaticanalysis/emptycase ● https://github.com/gostaticanalysis/notparam ● https://github.com/gostaticanalysis/unuseparam ● https://github.com/gostaticanalysis/signature ● https://github.com/gostaticanalysis/notest ● https://github.com/gostaticanalysis/lion ● https://github.com/gostaticanalysis/dupimport ● https://github.com/gostaticanalysis/ctxfield ● https://github.com/sharefull/refactortools

Slide 12

Slide 12 text

自作のAnalyzerコレクションを作る ■ unitchecker.Main関数に複数のAnalyzerを設定する 12 package main import ( "github.com/gostaticanalysis/forcetypeassert" "github.com/gostaticanalysis/nofmt" "github.com/gostaticanalysis/notest" "github.com/gostaticanalysis/vetgen/analyzers" "golang.org/x/tools/go/analysis/unitchecker" ) func main() { unitchecker.Main(append( analyzers.Govet(), // go vetと同じもの nofmt.Analyzer, notest.Analyzer, forcetypeassert.Analyzer, )...) }

Slide 13

Slide 13 text

tennvetを使う ■ tenntennさんの秘伝のタレ ● https://github.com/tenntenn/tennvet

Slide 14

Slide 14 text

knife(ナイフ) ■ go listのように型情報を表示できるツール ● https://github.com/gostaticanalysis/knife ● アーミーナイフのようにマルチに使えるツール ● 指定したパッケージ(Goファイル)に対し静的解析を行う ● 型情報をテンプレートをもとに出力する ○ コード生成にも利用ができる ● 下の例ではfmtパッケージの関数を表示している ○ grepコマンドでさらにPrintで始まる関数だけ絞っている $ knife -f "{{names .Funcs}}" fmt | grep "^Print" Print Printf Println 参考:https://engineering.mercari.com/blog/entry/mercari_codecast_3/ 14

Slide 15

Slide 15 text

リファクタリングする関数を見つける ■ gostaticanalysis/funcstat ● https://github.com/gostaticanalysis/funcstat ● 関数のサイズ、循環複雑度などを出力する $ funcstat encoding/json | head package,file,line,name,lines,bytes,params,returns,cyclomatic complexity encoding/json,decode.go,96,Unmarshal,87,3970,2,1,1 encoding/json,decode.go,132,Error,6,309,0,1,1 encoding/json,decode.go,149,Error,3,191,0,1,1 encoding/json,decode.go,159,Error,10,263,0,1,1 encoding/json,decode.go,170,unmarshal,15,320,1,1,1 encoding/json,decode.go,191,String,2,102,0,1,1 encoding/json,decode.go,194,Float64,4,132,0,2,1 encoding/json,decode.go,199,Int64,4,127,0,2,1 encoding/json,decode.go,222,readIndex,4,116,0,1,1

Slide 16

Slide 16 text

循環複雑度 ■ プログラムの複雑度を測る指標 ● 制御フローグラフ(Control Flow Graph)を用いる ● どのくらい分岐や繰り返しがあるか分かる ● E – N + 2P ○ E:エッジの数 ○ N:ノードの数(基本ブロックの数) ○ Pは単一の関数の場合は1 func f2(x, y int) int { if x == 2 { if y == 2 { if x+y == 4 { return x + y } } } return 0 }

Slide 17

Slide 17 text

制御フローグラフ(CFG)の取得 ■ x/go/analysis/passes/ctrlflowパッケージを使う ● https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/ctrlflow ● Analyzerを提供 ● 内部ではx/go/cfgを使用している ■ x/go/analysis/passes/buildssaパッケージを使う ● https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/buildssa ● 静的単一代入(SSA)形式に変換時にCFGが手に入る ● ssa.Function構造体がssa.BasicBlock構造体を保持

Slide 18

Slide 18 text

デモ

Slide 19

Slide 19 text

まとめ ■ 静的解析でリファクタリングをサポート ● 既存のコマンドでリファクタリング ○ 名前の変更、パッケージの変更 ● リファクタリングポイントを見つける ○ 良くないコードを探す ○ 定量的な情報を出力する