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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Hidetake Iwata
December 04, 2019
Programming
1
1.4k
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.8k
認証の仕組みとclient-go credential plugin / authentication and client-go credential plugin
int128
7
7.8k
CLIでOAuth/OIDCを快適に利用する
int128
0
980
AppEngine × Spring Boot × Kotlin
int128
0
160
いつものJIRA設定
int128
1
220
Swaggerのテンプレートを魔改造した話 / Customize Swagger Templates
int128
1
4.9k
本番環境のリリースを自動化した話
int128
0
830
Swagger × Spring Cloud
int128
0
130
The Evolution of System Architecture
int128
0
220
Other Decks in Programming
See All in Programming
Angular-Apps smarter machen mit Gen AI: Lokal und offlinefähig - Hands-on Workshop!
christianliebel
PRO
0
100
Claude Code Skill入門
mayahoney
0
240
CSC307 Lecture 14
javiergs
PRO
0
470
New in Go 1.26 Implementing go fix in product development
sunecosuri
0
420
20260313 - Grafana & Friends Taipei #1 - Kubernetes v1.36 的開發雜記:那些困在 Alpha 加護病房太久的 Metrics
tico88612
0
150
Go Conference mini in Sendai 2026 : Goに新機能を提案し実装されるまでのフロー徹底解説
yamatoya
0
560
The Past, Present, and Future of Enterprise Java
ivargrimstad
0
210
DSPy入門 Pythonで実現する自動プロンプト最適化 〜人手によるプロンプト調整からの卒業〜
seaturt1e
1
680
クライアントワークでSREをするということ。あるいは事業会社におけるSREと同じこと・違うこと
nnaka2992
1
330
AI Assistants for Your Angular Solutions
manfredsteyer
PRO
0
130
PostgreSQL を使った快適な go test 環境を求めて
otakakot
0
540
nilとは何か 〜interfaceの構造とnil!=nilから理解する〜
kuro_kurorrr
3
1.9k
Featured
See All Featured
From π to Pie charts
rasagy
0
150
KATA
mclloyd
PRO
35
15k
ピンチをチャンスに:未来をつくるプロダクトロードマップ #pmconf2020
aki_iinuma
128
55k
Producing Creativity
orderedlist
PRO
348
40k
The Impact of AI in SEO - AI Overviews June 2024 Edition
aleyda
5
760
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.6k
Building Flexible Design Systems
yeseniaperezcruz
330
40k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
290
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
5.9k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
69
How to Ace a Technical Interview
jacobian
281
24k
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