Goにおける静的解析と
製品開発への応用
The Go gopher was designed by Renee French.
The gopher stickers was made by Takuya Ueda.
Licensed under the Creative Commons 3.0
Attributions license.
2017/06/24(土)
ハッカーズチャンプルー
2017
1
自己紹介
メルカリ/ソウゾウ
上田拓也
twitter: @tenntenn
■ コミュニティ活動
Google Cloud Platform User Group (GCPUG) Tokyo
Goビギナーズ
golang.tokyo
Go Conference
■ 業務
GAE/Goでメルカリカウルを作ってます
GoやGCPコミュニティを盛り上げる仕事
Gopherを描く仕事(LINEスタンプ)
2
2017年7月1日(土)
BigQueryのハンズオン開催!
https://okipug.connpass.com/event/56374/
3
アジェンダ
● 静的解析とは?
● Goにおける静的解析の方法
● 静的解析の製品開発への応用
○ gpath, go-httpdoc
○ バナーツール
4
静的解析とは?
5
静的解析と動的解析
■ 静的解析
● プログラムを実行せずに解析すること
● ソースコードを解析する
● 例:lint, コード補完, コードフォーマッタ
■ 動的解析
● プログラムを実行して解析すること
● 実行時の変数の状態や関数の実行順などを検証
● 例:レースディテクション
6
静的解析とリフレクション
■ リフレクション
● 実行時に型や値の情報を解析する
■ Goのリフレクション
● reflectパッケージを使う
● 実行時にstructタグにアクセス唯一の方法
● 任意の型に値を入れたりする
● JSONやXMLなどのエンコーディングに使われる
7
静的解析とGoの開発ツール
■ Goは静的解析を開発ツールに使う事が多い
● gofmt/goimports
○ コードフォーマッター
● go vet/golint
○ コードチェッカー、リンター
● guru
○ 静的解析
● gocode
○ コード補完
● errcheck
○ エラー処理のチェック
● gorename/gomvpkg
○ リファクタリングツール
8
Goと静的解析
■ Goは静的解析がしやすい言語
● 静的型付け言語
● 文法がシンプル
● 暗黙の型変換がない
● 型推論
● 標準パッケージで静的解析機能を提供
9
静的解析でも多くの事が知れる
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 型チェックに関する機能を提供
10
Goにおける静的解析の方法
11
静的解析の流れ
12
ソースコード
トークン
抽象構文木(AST)
型情報
構文解析
字句解析
型チェック
go/scanner
go/token
go/parser
go/ast
go/types
go/constant
字句解析 - go/scanner,go/token
■ 文字列をトークンにしていく
● 空白などを取り除き、意味のある単位=トー
クンにしていく作業
13
IDENT ADD INT
トークン
ソースコード v + 1
v + 1
構文解析 - go/parser,go/ast
■ トークンを抽象構文木(AST)にしていく
● プログラムの構造を持たせる
14
IDENT ADD INT
ソースコード
+
v 1
BinaryExpr
Ident BasicLit
トークン
抽象構文木(AST)
n := 100 + 200
m := n + 300
型チェック - go/types,go/constant
■ 抽象構文木から型に関する情報を取得する
● 識別子の解決
● 型の推論
● 定数の評価
15
定数の評価
=300
型の推論
-> int
識別子の解決
抽象構文木(AST)の取得
■ go/parserパッケージの関数を使う
● ParseExpr,ParseExprFrom
○ 式をパースする
○ ParseExprはParseExprFromを簡易版
● ParseFile
○ ファイル単位でパースする
● ParseDir
○ ディレクトリ単位でパースする
○ 中でParseFileを呼んでいる
16
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Hello, Worldの抽象構文木の構成
17
Goの抽象構文木(AST)を手入力してHello, Worldを作る
http://qiita.com/tenntenn/items/0cbc6f1f00dc579fcd8c
Playgroundで動かす
*ast.File
[]ast.Decl
*ast.GenDecl *ast.FuncDecl
式の抽象構文木を取得する
■ 式単位を構文解析する
■ ParseExprFromでも書ける
18
expr, err := parser.ParseExpr(`v + 1`)
if err != nil {
/* エラー処理 */
}
/* exprを解析する処理 */
fset := token.NewFileSet() // ファイル情報
src := []byte(`v + 1`)
f := "" // ファイル名(式なので不要)
m := 0 // モード(式なので不要)
expr, err := parser.ParseExprFrom(fset, f, s, m)
const src = `
package main
var v = 100
func main() {
fmt.Println(v+1)
}`
fs := token.NewFileSet()
f, err := parser.ParseFile(fs, "my.go", src, 0)
if err != nil {
/* エラー処理 */
}
/* f を解析する処理 */
ソースから抽象構文木を取得する
■ Goのソースコードを構文解析する
19
引数はparse.ExprFromと
同じ構成
srcがnilだとファイル名
でファイルを開く
解析するファイルの中身
token.FileSetとは?
■ ファイル中の位置情報を記録する為の型
● 位置情報は数値で表される
● 複数のファイル間で一意の値
● 各ファイルのoffsetが記録されている
● パースする際に記録されていく
20
token.FileSetは出力引数として
Parse系の関数に渡す
*ast.BinaryExpr
*ast.Ident
*ast.BasicLit
v + 1
n, _ := parser.ParseExpr(`v + 1`)
ast.Inspect(n, func(n ast.Node) bool {
if n != nil { fmt.Printf("%Tn", n) }
return true
})
printer.Fprint(os.Stdout, token.NewFileSet(), n)
抽象構文木をトラバースする
■ ast.Inspectを使う
21
+
v 1
構文解析
抽象構文木(AST)を探索
抽象構文木(AST)を出力
BinaryExpr
Ident BasicLit
Playgroundで動かす
ast.Walkというのもある
func traverse(n ast.Node) {
switch n := n.(type) {
case *ast.Indent:
fmt.Println(n.Name)
case *ast.BinaryExpr:
traverse(n.X)
traverse(n.Y)
case *ast.UnaryExpr:
traverse(n.X)
}
}
抽象構文木をトラバースする
■ 再帰を使ってトラバースする
22
識別子の場合は名前を出力
二項演算式の場合は
各項を探索
単項演算式の場合は
項を探索
型でswitchする
Playgroundで動かす
ast.Nodeはインタフェース
参考資料
■ goパッケージで簡単に静的解析して世界を広げよう
● コードジェネレータ
○ ASTを取得する方法を調べる
○ 抽象構文木(AST)をトラバースする
○ 抽象構文木(AST)をいじってフォーマットをかける
○ Goの抽象構文木(AST)を手入力してHello, Worldを作る
○ go-app-builderのソースコードを読む
● リファクタリングツール
○ gorenameをライブラリとして使う
○ Goのスコープについて考えてみよう
○ go/typesパッケージを使い変数名をリネームしてみる
● 処理系
○ 簡単な式の評価機を作ってみる
○ 【実践goパッケージ】文字列から複素数型の値をパースする
○ もっと楽して式の評価器を作る
23
静的解析の製品開発への応用
例1:gpath, go-httpdoc
24
go-httpdoc
25
■ テストからAPIドキュメントを生成する
● https://github.com/mercari/go-httpdoc
● HTTPハンドラのテストから生成
● リクエストやレスポンスが記載される
● gRPCやJSON RPCなAPIでも利用可能
リクエスト/レスポンスのテスト
■ リクエストのテストからドキュメントを生成
● Request Bodyをリフレクションでテストしている
● 構造体の任意のフィールドを指定できる
26
validator.RequestBody(t, []httpdoc.TestCase{
{"Name", "tenntenn", "User Name"},
{"Attribute.Birthday", "1986-01-12", "User
birthday YYYY-MM-DD format"},
}, &createUserRequest{})
参考:go-httpdocのexample
tenntenn/gpath
■ 構造体の任意のフィールドにアクセスできる
● https://github.com/tenntenn/gpath
● 複雑なリフレクションが簡単に書ける
● Goの式で記述できる
● スライスやマップにも対応している
27
type Bar struct { N []int }
type Foo struct { Bar *Bar }
f := &Foo{ Bar: &Bar{ N: []int{100} }}
v, _ := At(f, `Bar.N[0]`)
fmt.Println(v)
開発ツールへ静的解析を使う
■ 21世紀なので自動化できるものは自動化する
● APIドキュメントの生成
● コードジェネレータ
● よくあるバグの指摘
○ go vetのプロジェクトカスタマイズ版
○ エラーの再代入
○ コンテキストの使い回し
○ structタグのミス
28
静的解析を用いて自作しよう
tenntenn/goq
■ Goの抽象構文木を簡単に検索できる
● https://github.com/tenntenn/goq
● 検索クエリを作ることができる
● 複雑な検索ができる
29
t := types.Universe.Lookup("error").Type()
errType := t.Underlying().(*types.Interface)
q := &goq.Type{
Identical: errType,
}
results := goq.New(fset, files, info).Query(q)
例:型がerrorのものだけ抽出
静的解析の製品開発への応用
例2:バナーツール
30
バナーツール
■ バナーの入稿を管理するツール
● 式で配信条件を設定
● 1ソースで複数の環境に対応
31
バナーツール
■ バナーの入稿を管理するツール
● 式で配信条件を設定
● 1ソースで複数の環境に対応
32
バナーツール
・配信条件
・レスポンス
バナー取得
OS, APIバージョン などの変数
条件に当てはまるバナー
画像URL, 遷移先URL
アプリ
デモ
33
YouTubeで見る
配信条件式の評価
■ バナーの配信条件に条件式を用いる
● Goの式としてパースできる独自形式
● 型定義を式で表現できるようにしてある
34
バナー取得
GET /banner/?os=1
String(os) == "1"
バナー画像URL
配信条件:
バナーツール
条件式からUIの自動生成
■UIを自動生成し簡単に入力できるにする
● 条件式からJSON Schemaを生成する
● JSON EditorでJSON SchemaからWeb UIを生成
35
os: iOS ▼
Android
UIを生成する部分
リクエストから
もらう部分
※一部簡略化している 生成したWeb UI
String(os) == Enum(__os, "0,1", "iOS,Android")
String(os) == "1"
UIで入力した値を式へ展開
静的解析を採用した効果
■ 1ソースで複数アプリに展開
● 4つ以上のアプリで採用
● バナー配信以外にも使える
■ 条件式からUIを自動生成
● アプリごとに高いカスタマイズ性を提供
● 非エンジニアでも利用可能
36
まとめ
■ Goは静的解析に向いている
● 静的型付け言語
● 標準パッケージで対応
■ 静的解析を開発ツールに使う
● よくあるレビュー指摘事項を自動化する
● 自作go vetやlintを作る
■ 開発ツール以外にも使える
● うまく使えば本番で稼働するコードも書ける
● 式としてパースできるのは強い
37
38
第6回Golang勉強会 in Okinawa
ハッカーズチャンプルー2017後夜祭
https://okinawa-go.doorkeeper.jp/events/61119
Thank you!
twitter: @tenntenn
Qiita: tenntenn
connpass: tenntenn
39

Goにおける静的解析と製品開発への応用