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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
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
3.1k
Android SDK with Docker
theoden9014
0
6k
ReactNativeのテスト紹介
theoden9014
0
1.2k
Other Decks in Technology
See All in Technology
The seven pitfalls of AI (revised version)
ufried
0
310
AIに攻撃される前に、AIに攻撃させる
tsuchikazu
0
170
ハードウェアコンペでもAI駆動開発が進んでいる話
iotengineer22
1
660
Incremental HTTP
kazuho
5
2.1k
オブザーバビリティを高める AI エージェント体験を考える / Designing AI Agent Experiences That Enhance Observability
aoto
PRO
2
320
不可逆な意思決定を避けながら10億超の顧客データを AI Ready にするデータ基盤設計
baseballyama
1
250
子育てエンジニアの可処分時間が減っても成長を諦めない戦い方
sudoakiy
6
4.3k
Google Cloud Next Tokyo 26登壇時のスクリプト
recruitengineers
PRO
0
220
組み立てて楽しむ AWS Blocks 入門
kmiya84377
0
220
二歩目のフロントエンド・ウェブアクセシビリティ
camiha
2
270
Kiro Crewをはじめよう
kizawa2020
1
120
実体験から学ぶ分析エージェントの開発と運⽤
recruitengineers
PRO
8
1.9k
Featured
See All Featured
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.4k
Exploring the relationship between traditional SERPs and Gen AI search
raygrieselhuber
PRO
4
4.3k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.9k
The Art of Programming - Codeland 2020
erikaheidi
57
14k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
Amusing Abliteration
ianozsvald
1
330
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
20k
Designing for Performance
lara
612
70k
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
1
620
Unsuck your backbone
ammeep
672
58k
Git: the NoSQL Database
bkeepers
PRO
433
67k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3.2k
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 を使って継続的にフィードバックできるようにする
ご清聴ありがとうございました