Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Welcome_to_Linter

 Welcome_to_Linter

Junki Kaneko

October 28, 2019
Tweet

More Decks by Junki Kaneko

Other Decks in Technology

Transcript

  1. 自己玹介 金子 淳貎 • SWET (Software Engineer in Test) •

    普段の業務 ◩ Go プロダクトの品質改善 ◩ CI や開発プロセスの改善 ◩ それらに䌎うツヌルやサヌビス開発
  2. 開発における起こりがちな問題 • コヌドレビュヌで本質的ではない指摘が倚くなっおしたいがち • コヌディングスタむル • 呜名芏則 • etc... •

    コヌド品質が開発者によっお違う • ゚ラヌを毎回チェックしおいないコヌド • panicが入っおいるミドルりェア • etc...
  3. Linter を䜿っお解決 • 䞀定のコヌド品質を機械的に担保できる • 䞀貫性 • コヌディングスタむルや呜名芏則 • 簡朔性

    • 無駄な凊理が入っおいないか、耇雑すぎないか • etc... • コヌドレビュヌする際の初期品質が向䞊 • コヌドレビュヌの負荷軜枛
  4. Go のコンパむルの流れ簡易版 1. ゜ヌスコヌド 2. トヌクン (go/token) a. ゜ヌスコヌドを読み蟌み、トヌクンぞ倉換する (go/scanner,

    go/token) 3. AST (go/ast) a. トヌクンを抜象構文朚 (AST) ぞ倉換する (go/parser) b. 型チェック (go/types, go/constant) 4. SSA圢匏 (golang.org/x/tools/go/ssa) 5. マシンコヌドの生成
  5. Go のコンパむルの流れ簡易版 1. ゜ヌスコヌド 2. トヌクン (go/token) a. ゜ヌスコヌドを読み蟌み、トヌクンぞ倉換する (go/scanner,

    go/token) 3. AST (go/ast) a. トヌクンを抜象構文朚 (AST) ぞ倉換する (go/parser) b. 型チェック (go/types, go/constant) 4. SSA圢匏 (golang.org/x/tools/go/ssa) 5. マシンコヌドの生成 基本的にLinterはAST, SSAを元にチェックを 行う
  6. Go のコンパむルの流れ簡易版 1. ゜ヌスコヌド 2. トヌクン (go/token) a. ゜ヌスコヌドを読み蟌み、トヌクンぞ倉換する (go/scanner,

    go/token) 3. AST (go/ast) a. トヌクンを抜象構文朚 (AST) ぞ倉換する (go/parser) b. 型チェック (go/types, go/constant) 4. SSA圢匏 (golang.org/x/tools/go/ssa) 5. マシンコヌドの生成 基本的にLinterはAST, SSAを元にチェックを 行う
  7. Go の抜象構文朚 (AST) 0 *ast.File { 1 . Package: helloworld/helloworld.go:1:1

    2 . Name: *ast.Ident { 3 . . NamePos: helloworld/helloworld.go:1:9 4 . . Name: "helloworld" 5 . } 6 . Decls: []ast.Decl (len = 1) { 7 . . 0: *ast.FuncDecl { 8 . . . Name: *ast.Ident { 9 . . . . NamePos: helloworld/helloworld.go:3:6 10 . . . . Name: "HelloWorld" 11 . . . . Obj: *ast.Object { 12 . . . . . Kind: func 13 . . . . . Name: "HelloWorld" 14 . . . . . Decl: *(obj @ 7) 15 . . . . } 16 . . . } 17 . . . Type: *ast.FuncType { 18 . . . . Func: helloworld/helloworld.go:3:1 19 . . . . Params: *ast.FieldList { 20 . . . . . Opening: helloworld/helloworld.go:3:16 21 . . . . . Closing: helloworld/helloworld.go:3:17 22 . . . . } 23 . . . . Results: *ast.FieldList { 24 . . . . . Opening: - 25 . . . . . List: []*ast.Field (len = 1) { 26 . . . . . . 0: *ast.Field { 27 . . . . . . . Type: *ast.Ident { 28 . . . . . . . . NamePos: helloworld/helloworld.go:3:19 29 . . . . . . . . Name: "string" 30 . . . . . . . } 31 . . . . . . } 32 . . . . . } 33 . . . . . Closing: - . . . . . package helloworld func HelloWorld() string { return "Hellow World" }
  8. Go における Linter - golang.org/x/tools/go/analysis パッケヌゞによっおフレヌムワヌク化されおいる - analysis.Analyzer が1モゞュヌルずなっおいる -

    go tool vetも耇数のanalysis.Analyzerを利甚しお実装しおいる - Analyzerの䞭で䟝存関係を持たせ、 埗られた結果を他の Analyzerで利甚するこずが可胜になっおいる package main import ( "cmd/internal/objabi" "golang.org/x/tools/go/analysis/unitchecker" "golang.org/x/tools/go/analysis/passes/asmdecl" "golang.org/x/tools/go/analysis/passes/assign" "golang.org/x/tools/go/analysis/passes/atomic" "golang.org/x/tools/go/analysis/passes/bools" "golang.org/x/tools/go/analysis/passes/buildtag" "golang.org/x/tools/go/analysis/passes/cgocall" ... ) func main() { objabi.AddVersionFlag() unitchecker.Main( asmdecl.Analyzer, assign.Analyzer, atomic.Analyzer, bools.Analyzer, buildtag.Analyzer, cgocall.Analyzer, ... )
  9. analysis.Analyzer • 静的解析をモゞュヌル化する際の 1モゞュヌル単䜍ずなるもの • Linter を開発する際は Analyzer 単䜍で実装しおいく var

    Analyzer = &analysis.Analyzer{ Name: "sample", // Analyzerの名前 Doc: "this is sample", // Analyzerに぀いおの説明 Requires: []*analysis.Analyzer{inspect.Analyzer}, // 䟝存するAnalyzerのリスト Run: run, // 実際の解析凊理をここに蚘述するパッケヌゞ単䜍で実行される FactTypes: nil, // このAnalyzer内で耇数パッケヌゞを跚いだデヌタ共有等を行いたい堎合に利甚する ResultType: nil, // このAnalyzerが解析した結果を他のAnalyzerにも提䟛したい堎合はここで型を宣蚀する }
  10. 分析噚専甚の analysis.Analyzer ★ analysis/passes/inspect : ASTを扱える ◩ *inspector.Inspector ★ analysis/passes/ctrlflow

    : 制埡フロヌグラフを扱える ◩ *cfg.CFG ★ analysis/passes/buildssa : SSA圢匏を扱える ◩ *ssa.Package, []*ssa.Function
  11. analysis.Analyzer.Run • シグネチャ: func(*Pass) (interface{}, error) • 匕数: analysis.Pass •

    この構造䜓から怜査察象のパッケヌゞのデヌタを取埗し、その結果を栌玍する • 戻り倀: interface{}, error • 第䞀戻り倀に先ほど初期化時に定矩した ResultTypeの型の倀を返すこずで、 他のAnalyzerからその倀を利甚するこずができる
  12. analysis.Analyzer で ASTを扱う • analysis/passes/inspect.Analyzer をRequiresフィヌルドに含める • golang.org/x/tools/go/ast/inspectorパッケヌゞの inspector.Inspector を䜿っお

    ASTのNodeに察しおの凊理を蚘述しおいく func run(pass *analysis.Pass) (interface{}, error) { // inspect.Analyzerが提䟛する結果を利甚する inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) // 凊理を適甚したいast.Nodeの皮類をフィルタリングできる nodeFilter := []ast.Node{ (*ast.CallExpr)(nil), } inspect.Preorder(nodeFilter, func(n ast.Node) { // Node毎の凊理を蚘述 }) return nil, nil }
  13. analysis/analysistest を䜿ったテスト • testdata ディレクトリを䜜成するずそこを GOPATHずしお扱うこずができる • ディレクトリ配䞋に察しお、䜜成した Linter を実行するこずができる

    tests ├── testdata │ └── src │ ├── a │ │ ├── a.go │ │ ├── a_test.go │ │ └── ax_test.go │ ├── b │ │ └── b.go │ ├── b_x_test │ │ └── b_test.go │ └── divergent │ ├── buf.go │ └── buf_test.go ├── tests.go └── tests_test.go golang.org/x/tools/go/analysis/passes/tests より // tests_test.go func Test(t *testing.T) { testdata := analysistest.TestData() analysistest.Run(t, testdata, tests.Analyzer, "a", // loads "a", "a [a.test]", and "a.test" "b_x_test", // loads "b" and "b_x_test" "divergent", ) }
  14. analysis/analysistest を䜿ったテスト - Assertion • コメントでアサヌションを行うこずができる tests ├── testdata │

    └── src │ ├── a │ │ ├── a.go │ │ ├── a_test.go │ │ └── ax_test.go │ ├── b │ │ └── b.go │ ├── b_x_test │ │ └── b_test.go │ └── divergent │ ├── buf.go │ └── buf_test.go ├── tests.go └── tests_test.go // testdata/src/a/a_test.go func ExamplePuffer_suffix () {} // want "ExamplePuffer_suffix refers to unknown identifier: Puffer" func ExampleFoo () {} // OK because a.Foo exists func ExampleBar () {} // want "ExampleBar refers to unknown identifier: Bar"
  15. analysis.Analyzer のコマンド゚ントリヌポむント ★ analysis/unitchecker.Main ◩ Goの内郚 (cmd/go/internal/work)で利甚するための゚ントリヌポむント ▪ ナヌザは基本的には go

    vet -vettool 経由でしか扱えない ★ analysis/singlechecker.Main ◩ 単独のコマンドラむンずしおも実行できる ▪ go vet経由でも扱える ★ analysis/multichecker.Main ◩ 単独のコマンドラむンずしおも実行できる ▪ go vet経由でも扱える
  16. go vet -vettool • go vet コマンドのデフォルトの動䜜 • デフォルトではunitcheckerで実装しおいるgo tool

    vet$GOTOOLDIR/vetを -vettoolに蚭定しお実行しおいる • -vettoolで 特定のLinterを指定した堎合、go tool vetは実行されない
  17. 参考になる analysis.Analyzer の実装や資料 • analysis.Analyzer のテンプレヌト生成ツヌル • https://github.com/gostaticanalysis/skeleton • analysis.Analyzer

    のサンプル • https://github.com/golang/tools/tree/master/go/analysis/passes • GoのためのGo • https://motemen.github.io/go-for-go-book/
  18. プロダクトに導入するステップ 1. CI を導入する 今回は觊れたせん 2. 既存の Linter を導入する 3. CI

    に組み蟌む 4. ドメむン固有のルヌルが必芁になったら Linterの開発を怜蚎する
  19. 既存の Linter • go vet • コンパむラで怜知できない゚ラヌを怜知 Printfのフォヌマットず匕数の怜査 , etc...

    • golint • コヌディングスタむルの怜査 • golangci-lint • 様々な Linter のアグリゲヌタヌ 他にも様々なLinterツヌルがある https://github.com/golangci/awesome-go-linters
  20. 既存の Linter を導入する - golangci-lint • https://github.com/golangci/golangci-lint • どのルヌルを適甚するか決める •

    .golangci.yml をプロゞェクトルヌトに䜜成 • https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml • サポヌトされおいる党おのオプションが説明されおいる
  21. CI に組み蟌む - reviewdog • https://github.com/reviewdog/reviewdog • Linter の結果を GitHub

    などの Pull Request 差分にコメントしおくれるツヌル