Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Welcome_to_Linter
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Junki Kaneko
October 28, 2019
Technology
18k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Welcome_to_Linter
Junki Kaneko
October 28, 2019
More Decks by Junki Kaneko
See All by Junki Kaneko
DeNA TechCon 2021 - 自動テストのないプロダクトの開発効率化への道
theoden9014
0
220
Jenkins のつらみを軽減した話
theoden9014
5
3k
Android SDK with Docker
theoden9014
0
5.9k
ReactNativeのテスト紹介
theoden9014
0
1.2k
Other Decks in Technology
See All in Technology
AI時代のPlaywright活用(システムテストを自動化する ー 実行エンジンにPla ywrightを選んだ理由)
ynisqa1988
2
1k
AI時代の開発生産性を捉え直す — 経営と現場をつなぐ「開発組織のオブザーバビリティ」— / AI Dev Ex Conference 2026
tkyowa
1
1.7k
A Bag-of-Documents Model for Query Specificity
dtunkelang
0
110
Playwright × AI Agent でE2Eテストはどう変わるか AI駆動テストの可能性と実用検証の結果
taiga7543
2
880
複数プロダクトで進めるAI機能実装 ── 実践から得たリアルな学びとロードマップ実現への挑戦 / AICon2026_yanari
rakus_dev
1
310
AI Agent を本番環境へ―― Microsoft Foundry × Azure Serverless で作る Enterprise-Ready な基盤
shibayan
PRO
1
530
コンテナ・K8s研修【MIXI 26新卒技術研修】#2
mixi_engineers
PRO
1
200
全社でのソフトウェアサプライチェーン攻撃対策をやってみた with Takumi Guard
z63d
0
310
AIツールを導入しても生産性はあがらない? カオナビが直面した 3つの壁と乗り越え方。/ Overcoming 3 Barriers to AI-Driven Productivity at kaonavi
kaonavi
0
320
テックカンファレンス三大ステークホルダーの文化人類学 ─ 違いを認め合う関係性作り
bash0c7
3
760
20260720_クラウド女子会×PyLadiesTokyoコラボ Amazon Bedrock ハンズオン用資料
yuuka51
2
120
AI時代こそ、スケールしないことをしよう -「作る人」から「なぜ作るか」を考える人へ / Do Things That Don't Scale in the AI Era — From How to Why
kaminashi
1
160
Featured
See All Featured
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
The Illustrated Guide to Node.js - THAT Conference 2024
reverentgeek
1
410
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
420
The Cost Of JavaScript in 2023
addyosmani
55
10k
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.6k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.3k
Practical Orchestrator
shlominoach
191
11k
Heart Work Chapter 1 - Part 1
lfama
PRO
8
36k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
2.9k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
330
Building the Perfect Custom Keyboard
takai
2
820
Transcript
Welcome to Linter Go Conference 2019 Autumn Junki Kaneko
自己紹介 金子 淳貴 • SWET (Software Engineer in Test) •
普段の業務 ◦ Go プロダクトの品質改善 ◦ CI や開発プロセスの改善 ◦ それらに伴うツールやサービス開発
この発表のゴール • Goのプロダクトにおいて、 Linter 導入やカスタムLinterの開発に関しての 一通り必要な知識を知ること
Agenda 1. 背景 2. Go における静的解析 3. カスタムLinter の開発 4.
プロダクトに Linter を導入する
1. 背景
そもそも Linter とは ? コンパイラで検出できないエラーや構文を静的解析によって検査するツールのこと
開発における起こりがちな問題 • コードレビューで本質的ではない指摘が多くなってしまいがち • コーディングスタイル • 命名規則 • etc... •
コード品質が開発者によって違う • エラーを毎回チェックしていないコード • panicが入っているミドルウェア • etc...
Linter を使って解決 • 一定のコード品質を機械的に担保できる • 一貫性 • コーディングスタイルや命名規則 • 簡潔性
• 無駄な処理が入っていないか、複雑すぎないか • etc... • コードレビューする際の初期品質が向上 • コードレビューの負荷軽減
2. Go における静的解析
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. マシンコードの生成
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を元にチェックを 行う
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を元にチェックを 行う
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" }
3. カスタムLinterの開発
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, ... )
カスタムLinterの開発手順 1. analysis.Analyzer を実装する 2. analysistest を使って analysis.Analyzer をテストする 3.
コマンド化
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にも提供したい場合はここで型を宣言する }
分析器専用の analysis.Analyzer ★ analysis/passes/inspect : ASTを扱える ◦ *inspector.Inspector ★ analysis/passes/ctrlflow
: 制御フローグラフを扱える ◦ *cfg.CFG ★ analysis/passes/buildssa : SSA形式を扱える ◦ *ssa.Package, []*ssa.Function
analysis.Analyzer.Run • シグネチャ: func(*Pass) (interface{}, error) • 引数: analysis.Pass •
この構造体から検査対象のパッケージのデータを取得し、その結果を格納する • 戻り値: interface{}, error • 第一戻り値に先ほど初期化時に定義した ResultTypeの型の値を返すことで、 他のAnalyzerからその値を利用することができる
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 }
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", ) }
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"
analysis.Analyzer のコマンドエントリーポイント ★ analysis/unitchecker.Main ◦ Goの内部 (cmd/go/internal/work)で利用するためのエントリーポイント ▪ ユーザは基本的には go
vet -vettool 経由でしか扱えない ★ analysis/singlechecker.Main ◦ 単独のコマンドラインとしても実行できる ▪ go vet経由でも扱える ★ analysis/multichecker.Main ◦ 単独のコマンドラインとしても実行できる ▪ go vet経由でも扱える
go vet -vettool • go vet コマンドのデフォルトの動作 • デフォルトではunitcheckerで実装しているgo tool
vet($GOTOOLDIR/vet)を -vettoolに設定して実行している • -vettoolで 特定のLinterを指定した場合、go tool vetは実行されない
参考になる 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/
4. プロダクトにLinterを導入する
プロダクトに導入する際の注意点 • テストと同様に、開発フローの一部として継続的に回す必要がある • CI に組み込む • 開発初期段階から導入しておくべき • 開発途中から導入する際はルールを少なくして、徐々に増やしていくと良い
• テストと同様に常にレッドである状態が続いてしまうと麻痺してしまう
プロダクトに導入するステップ 1. CI を導入する (今回は触れません) 2. 既存の Linter を導入する 3. CI
に組み込む 4. ドメイン固有のルールが必要になったら Linterの開発を検討する
既存の Linter • go vet • コンパイラで検知できないエラーを検知( Printfのフォーマットと引数の検査 , etc...)
• golint • コーディングスタイルの検査 • golangci-lint • 様々な Linter のアグリゲーター 他にも様々なLinterツールがある https://github.com/golangci/awesome-go-linters
既存の Linter を導入する - golangci-lint • https://github.com/golangci/golangci-lint • どのルールを適用するか決める •
.golangci.yml をプロジェクトルートに作成 • https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml • サポートされている全てのオプションが説明されている
CI に組み込む - reviewdog • https://github.com/reviewdog/reviewdog • Linter の結果を GitHub
などの Pull Request 差分にコメントしてくれるツール
ドメイン固有の カスタムLinter • golangci-lintとカスタムLinterを1コマンドで実行できるようにする • 手元でも簡単に確認できるように • makeやshell scriptを使うと良い •
reviewdogでフィードバックできるようにする
Summary • 一般的なLintルールは golangci-lint を使うと良い • 独自のカスタムLinterが必要になったら analysis.Analyzer を使って実装 •
CI と reviewdog を使って継続的にフィードバックできるようにする
ご清聴ありがとうございました