Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Welcome_to_Linter
Search
Junki Kaneko
October 28, 2019
Technology
19k
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
230
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
Reactの設計論
uhyo
24
14k
SREは、MCPとAutopilotをこう使え!
kazumax55
3
890
俺の仕事は AIに奪われないし、たぶんその BIも要らない
hikaruri
0
520
AIエージェントの一手は 誰も見ていない - Falco拡張OSS「Prempti」とeBPFで サーバーレス実行基盤を二層防御する
keitah
0
200
積み重なった技術負債への挑戦 〜初手としての全社ゴト化〜
techtekt
PRO
0
1.3k
Railsのように考える: See through the Master
snoozer05
PRO
4
1.1k
Cloudflare Workers 向けアプリを C# で構築する ~WASM Native AOT への道~
nenonaninu
1
280
Genieを崇めよ
kameitomohiro
0
160
顧客に向き合う開発組織へ。リアーキテクチャとフィーチャーチーム化で挑む組織改革
safie
0
2.1k
2026-09-11 【Snowflake World Tour Tokyo 2026】Snowflakeを起点に、AI Agentが自律稼働し続ける未来へ / Driving AI Agents with Snowflake
civitaspo
0
700
AIエージェントの自己改善をどう設計するか / How to Design Self-Improvement for AI Agents
22mi
25
16k
データ_AIの事業の勝敗をわけるもの
nek0128
1
450
Featured
See All Featured
Crafting Experiences
bethany
1
340
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
890
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.3k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.6k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
46k
Are puppies a ranking factor?
jonoalderson
2
3.9k
職位にかかわらず全員がリーダーシップを発揮するチーム作り / Building a team where everyone can demonstrate leadership regardless of position
madoxten
69
65k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
12
1.3k
Everyday Curiosity
cassininazir
0
320
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
300
How to Think Like a Performance Engineer
csswizardry
28
2.8k
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 を使って継続的にフィードバックできるようにする
ご清聴ありがとうございました