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
Rewrite Go error handling using AST transformation
Search
Hidetake Iwata
December 04, 2019
Programming
1
1.3k
Rewrite Go error handling using AST transformation
golang.tokyo #28
2019.12.4
Hidetake Iwata
December 04, 2019
Tweet
Share
More Decks by Hidetake Iwata
See All by Hidetake Iwata
Cluster AutoscalerをTerraformとHelmfileでデプロイしてPrometheusでモニタリングする / Deploy the Cluster Autoscaler with Terraform and Helmfile, Monitor with Prometheus
int128
3
1.6k
認証の仕組みとclient-go credential plugin / authentication and client-go credential plugin
int128
7
7.3k
CLIでOAuth/OIDCを快適に利用する
int128
0
770
AppEngine × Spring Boot × Kotlin
int128
0
90
いつものJIRA設定
int128
1
160
Swaggerのテンプレートを魔改造した話 / Customize Swagger Templates
int128
1
4.7k
本番環境のリリースを自動化した話
int128
0
710
Swagger × Spring Cloud
int128
0
83
The Evolution of System Architecture
int128
0
160
Other Decks in Programming
See All in Programming
ATDDで素早く安定した デリバリを実現しよう!
tonnsama
1
1.9k
Итераторы в Go 1.23: зачем они нужны, как использовать, и насколько они быстрые?
lamodatech
0
1.4k
EC2からECSへ 念願のコンテナ移行と巨大レガシーPHPアプリケーションの再構築
sumiyae
3
590
Оптимизируем производительность блока Казначейство
lamodatech
0
950
テストコードのガイドライン 〜作成から運用まで〜
riku929hr
7
1.4k
.NETでOBS Studio操作してみたけど…… / Operating OBS Studio by .NET
skasweb
0
120
Асинхронность неизбежна: как мы проектировали сервис уведомлений
lamodatech
0
1.3k
ゼロからの、レトロゲームエンジンの作り方
tokujiros
3
1.1k
Alba: Why, How and What's So Interesting
okuramasafumi
0
210
PHPUnitしか使ってこなかった 一般PHPerがPestに乗り換えた実録
mashirou1234
0
420
盆栽転じて家具となる / Bonsai and Furnitures
aereal
0
1.9k
Scaling your build logic
antalmonori
1
100
Featured
See All Featured
Six Lessons from altMBA
skipperchong
27
3.6k
Adopting Sorbet at Scale
ufuk
74
9.2k
Keith and Marios Guide to Fast Websites
keithpitt
410
22k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
26
1.9k
Practical Orchestrator
shlominoach
186
10k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
The Illustrated Children's Guide to Kubernetes
chrisshort
48
49k
4 Signs Your Business is Dying
shpigford
182
22k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
7
570
Rebuilding a faster, lazier Slack
samanthasiow
79
8.8k
GitHub's CSS Performance
jonrohan
1030
460k
Intergalactic Javascript Robots from Outer Space
tanoku
270
27k
Transcript
Rewrite Go error handling using AST transformation golang.tokyo #28 Hidetake
Iwata (@int128)
岩田 英丈 / Hidetake Iwata 2 Software Engineer at NTT
DATA (Senior YAML/Terraform Engineer ) Open Source Developer at https://github.com/int128
Agenda 3 お話しすること • Goにおける抽象構文木の変換と操作 • アプリケーションのエラー処理を書き換える方法 お話ししないこと • Goにおけるエラー処理の詳細
Error handling in Go Goでエラー処理を行うには主に以下の方法がある • errorsパッケージを利用する(Go 1.13で拡張された) • golang.org/x/xerrors
パッケージを利用する • github.com/pkg/errors パッケージを利用する 他にもサードパーティのパッケージが公開されている (e.g. github.com/morikuni/failure) 4
以下のような目的で、アプリケーション内のエラー処理を別の方法に書き換えたいこ とがある • 特定の機能を使いたい(e.g. エラー型判定、スタックトレース) • 外部パッケージを使いたくない • ソースコードの可読性を改善したい Rewrite
the Go error handling 5
例:pkg/errors.Wrapf() を xerrors.Errorf() に 書き換える • import文を変更する • 関数名を変更する •
引数の順序を入れ替える • 引数のフォーマット文字列に : %w を付け加える 6 // pkg/errorsの場合 return nil, errors.Wrapf(err, "item id=%s not found", id) // xerrorsの場合 return nil, xerrors.Errorf("item id=%s not found: %w", id, err)
簡単な書き換えは正規表現による一括置換でできるが、 複雑な書き換えには以下の手法が広く利用されている 1. ソースコードを抽象構文木に変換する 2. 抽象構文木を操作する 3. 抽象構文木をソースコードに書き出す @tenntennさんのスライドが分かりやすいです https://www.slideshare.net/takuyaueda967/go-74970321
抽象構文木の操作による書き換え 7
golang.org/x/tools/go/packages パッケージ等を利用すると、ソースコードと抽象 構文木を相互に変換できる Goのソースコードと抽象構文木の相互変換 https://godoc.org/golang.org/x/tools/go/packages cfg := &packages.Config{/* … */}
pkgs, err := packages.Load(cfg, pkgNames...) // ソースコードを解析する if err != nil {/* … */} if packages.PrintErrors(pkgs) > 0 {/* … */} for _, pkg := range pkgs { for _, file := range pkg.Syntax { err := printer.Fprint(os.Stdout, pkg.Fset, file) // 抽象構文木を出力する } } 8
0 *packages.Package { 1 . ID: "_/hello-go-ast-transformation" 2 . Name:
"" 3 . PkgPath: "" 4 . CompiledGoFiles: []string (len = 1) { 5 . . 0: "/hello-go-ast-transformation/main.go" 6 . } 7 . ExportFile: "" 8 . Types: *types.Package {} 9 . Fset: *token.FileSet {} 10 . IllTyped: false 11 . Syntax: []*ast.File (len = 1) { 12 . . 0: *ast.File { 13 . . . Package: /hello-go-ast-transformation/main.go:1:1 14 . . . Name: *ast.Ident { 15 . . . . NamePos: /hello-go-ast-transformation/main.go:1:9 16 . . . . Name: "main" 17 . . . } 18 . . . Decls: []ast.Decl (len = 3) { 19 . . . . 0: *ast.GenDecl { 20 . . . . . TokPos: /hello-go-ast-transformation/main.go:3:1 21 . . . . . Tok: import 22 . . . . . Lparen: /hello-go-ast-transformation/main.go:3:8 23 . . . . . Specs: []ast.Spec (len = 5) { 24 . . . . . . 0: *ast.ImportSpec { 25 . . . . . . . Path: *ast.BasicLit { packages.Load() が返す抽象構文木の基本構造 パッケージ ファイル ファイル import宣言 関数宣言 文 文 https://github.com/int128/hello-go-ast-transformation 9
148 1: *ast.CallExpr { 149 . Fun: *ast.SelectorExpr { 150
. . X: *ast.Ident { 151 . . . NamePos: hello.go:12:14 152 . . . Name: "errors" 153 . . } 154 . . Sel: *ast.Ident { 155 . . . NamePos: hello.go:12:21 156 . . . Name: "Wrapf" 157 . . } 158 . } 159 . Lparen: hello.go:12:26 160 . Args: []ast.Expr (len = 3) { 161 . . 0: *ast.Ident { 162 . . . NamePos: hello.go:12:27 163 . . . Name: "err" 164 . . . Obj: *(obj @ 107) 165 . . } 166 . . 1: *ast.BasicLit { 167 . . . ValuePos: hello.go:12:32 168 . . . Kind: STRING 169 . . . Value: "\"item id=%s not found\"" 170 . . } 171 . . 2: *ast.Ident { 172 . . . NamePos: hello.go:12:56 173 . . . Name: "id" 174 . . . Obj: *(obj @ 33) 175 . . } 176 . } errors.Wrapf(err, "item id=%s not found", id) に対応する部分木 CallExpr Selector Expr Ident “errors” Ident “Wrapf” Ident “err” BasicLit “item...” Ident “id” []Expr 10
156 1: *ast.CallExpr { 157 . Fun: *ast.SelectorExpr { 158
. . X: *ast.Ident { 159 . . . NamePos: hello.go:13:14 160 . . . Name: "xerrors" 161 . . } 162 . . Sel: *ast.Ident { 163 . . . NamePos: hello.go:13:22 164 . . . Name: "Errorf" 165 . . } 166 . } 167 . Lparen: hello.go:13:28 168 . Args: []ast.Expr (len = 3) { 169 . . 0: *ast.BasicLit { 170 . . . ValuePos: hello.go:13:29 171 . . . Kind: STRING 172 . . . Value: "\"item id=%s not found: %w\"" 173 . . } 174 . . 1: *ast.Ident { 175 . . . NamePos: hello.go:13:57 176 . . . Name: "id" 177 . . . Obj: *(obj @ 41) 178 . . } 179 . . 2: *ast.Ident { 180 . . . NamePos: hello.go:13:61 181 . . . Name: "err" 182 . . . Obj: *(obj @ 115) 183 . . } 184 . } xerrors.Errorf("item id=%s not found: %w", id, err) に対応する部分木 CallExpr Selector Expr Ident “xerrors” Ident “Errorf” Ident “err” BasicLit “item...” Ident “id” []Expr 11
pkg/errors.Wrapf() を xerrors.Errorf() に書き換える = 抽象構文木を操作する CallExpr Selector Expr Ident
“errors” Ident “Wrapf” Ident “err” BasicLit “item...” Ident “id” []Expr CallExpr Selector Expr Ident “xerrors” Ident “Errorf” Ident “err” BasicLit “item...” Ident “id” []Expr pkg/errors.Wrapf() の部分木 xerrors.Errorf() の部分木 12
抽象構文木の探索 go/astパッケージの Inspect() や Walk() を利用すると、抽象構文木を深さ優先探索 できる ast.Inspect(file, func(node ast.Node)
bool { switch node := node.(type) { case *ast.CallExpr: // 関数呼び出しの場合 switch fun := node.Fun.(type) { case *ast.SelectorExpr: switch x := fun.X.(type) { case *ast.Ident: if x.Sel.Name == "errors" { // パッケージ名がerrorsの場合 x.Sel.Name = "xerrors" // パッケージ名を書き換える } 13 https://golang.org/pkg/go/ast/#Inspect
関数呼び出しの型解決 抽象構文木のノードには型情報が含まれないので、 そのままでは変数や関数呼び出しの実体を判断できない 例:ソースコードに書かれているerrorsの実体はどれでしょう? • 標準パッケージ • 別名インポートされたパッケージ? • パッケージスコープのerrors変数?
14 func HelloWorld() error { return errors.New("hello") }
go/typesによる型解決 15 go/typesパッケージの Info.ObjectOf() で識別子の型を解決できる ast.Inspect(file, func(node ast.Node) bool {
switch node := node.(type) { case *ast.CallExpr: // 関数呼び出しの場合 switch fun := node.Fun.(type) { case *ast.SelectorExpr: switch x := fun.X.(type) { case *ast.Ident: switch o := pkg.TypesInfo.ObjectOf(x).(type) { case *types.PkgName: // 関数呼び出しの左辺がパッケージの場合 pkgPath := o.Imported().Path() https://golang.org/pkg/go/types/#Info.ObjectOf
まとめ Goにおける抽象構文木の変換と操作を用いて、アプリケーションのエラー処理を書 き換える方法を紹介しました アプリケーションのエラー処理を書き換えるツールを作っているので、 よかったらフィードバックをお願いします!! ✨ https://github.com/int128/transerr 16
We are hiring! www.nttdata-careers.com 17