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
Go でのエラー生成パターン
Search
KoharaKazuya
July 26, 2019
Programming
0
16k
Go でのエラー生成パターン
Umeda.go 2019 Summer
KoharaKazuya
July 26, 2019
Tweet
Share
More Decks by KoharaKazuya
See All by KoharaKazuya
Native File System API の紹介と Zip を作る Web サービスを作った話
koharakazuya
0
160
グラフィカルに URL を編集できるサービスを作った
koharakazuya
1
15k
pickpatch を作った話と仕組みの解説
koharakazuya
0
50
Other Decks in Programming
See All in Programming
来たるべき 8.0 に備えて React 19 新機能と React Router 固有機能の取捨選択とすり合わせを考える
oukayuka
2
870
第9回 情シス転職ミートアップ 株式会社IVRy(アイブリー)の紹介
ivry_presentationmaterials
1
250
生成AIコーディングとの向き合い方、AIと共創するという考え方 / How to deal with generative AI coding and the concept of co-creating with AI
seike460
PRO
1
340
Is Xcode slowly dying out in 2025?
uetyo
1
220
Go1.25からのGOMAXPROCS
kuro_kurorrr
1
820
Blazing Fast UI Development with Compose Hot Reload (droidcon New York 2025)
zsmb
1
260
Rubyでやりたい駆動開発 / Ruby driven development
chobishiba
1
490
Code as Context 〜 1にコードで 2にリンタ 34がなくて 5にルール? 〜
yodakeisuke
0
110
Railsアプリケーションと パフォーマンスチューニング ー 秒間5万リクエストの モバイルオーダーシステムを支える事例 ー Rubyセミナー 大阪
falcon8823
4
1k
Azure AI Foundryではじめてのマルチエージェントワークフロー
seosoft
0
140
#QiitaBash MCPのセキュリティ
ryosukedtomita
0
250
LINEヤフー データグループ紹介
lycorp_recruit_jp
0
1.4k
Featured
See All Featured
Into the Great Unknown - MozCon
thekraken
39
1.9k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
53
2.8k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
10
940
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.7k
The Language of Interfaces
destraynor
158
25k
Six Lessons from altMBA
skipperchong
28
3.9k
Designing for humans not robots
tammielis
253
25k
Building a Modern Day E-commerce SEO Strategy
aleyda
42
7.4k
Visualization
eitanlees
146
16k
A designer walks into a library…
pauljervisheath
207
24k
Why Our Code Smells
bkeepers
PRO
337
57k
The Web Performance Landscape in 2024 [PerfNow 2024]
tammyeverts
8
670
Transcript
Go でのエラー⽣成パターン Umeda.go 2019 Summer
今⽇話すこと 「Go でどんな error を⽣成するか」 のいくつかのパターンを紹介 & ⽐較する 2 2
⾃⼰紹介 ⼩原 ⼀哉 (こはら かず や) ウェブエンジニア フェンリル株式会社 @KoharaKazuya 3
3
背景 「Go がそこそこ書けるようになってきたぞ。 よし、エラー処理を書こう」 「あれ、エラーの書き⽅は⾊々あるっぽい」 「⾊々あるけどどう違うんだろう? どれがいいんだろう?」 4 4
まずは⾃分の求めているもの明確にする 5 5
私が「エラー」に求めるもの 1. わかりやすいメッセージを出⼒できること エラー発⽣時にすぐに原因を理解するため 2. プログラムがエラーの種類を識別できること 回復や変換などのエラーハンドリングのため 3. スタックトレースを持つこと エラー発⽣時に発⽣した箇所を知るため
6 6
私が「エラー」に求めるもの 4. エラーの原因のエラーを辿れること エラーの原因のエラーが存在するなら そちらの解決が必要な場合がある 5. エラー種別ごとに追加の情報を持てること エラーによっては追加の情報を プログラムに読ませたい場合がある 例:
HTTP Retry-After ヘッダー 7 7
開発者が 読めることと プログラムが 読めることを 混ぜて考えない ↓ エラーの種類の識別はプログラムのため メッセージは開発者のため スタックトレースの表⽰は開発者のため エラーの原因を辿るのは開発者のため
※ 1 ※1 理想的にはプログラムは直接呼び出す API が定義するエラーのみ相⼿にすれば良い。 エラーを⽣成する側は原因となったエラーを「⾃分の⾔葉に翻訳して」エラーを ⽣成し直すべき。エラーの原因を辿るのは知るためで挙動を変えるためではないはず。 8 8
これらの観点で様々なエラーの⽅法を ⽐較していく 9 9
メッセージ⽂字列チェック エラーメッセージの⽂字列を使って種別を判定する⽅法 → やるべきではない 開発者向けのメッセージをプログラムに読ませるべきではない 10 10
No Handling if err != nil { return err }
→ やるべきではない 利⽤者にとって知らないエラーが出るので基本は避けるべき。 例: 「両替」ロジックの API を使ったら `permission denied` というエラーが出てきた 11 11
翻訳 if err != nil { return fmt.Errorf("database error: %v",
err) } 下位のエラーを翻訳して上位に投げるだけ 12 12
翻訳 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.
種類の識別 - その場限りの値のため 3. スタックトレース - fmt.Errorf により 4. エラーの原因 ✔ %v に⼊る 5. 追加情報 - ⽂字列のみ 13 13
Sentinel Errors // 定義 var ( ErrInvalid = errors.New("invalid argument")
ErrPermission = errors.New("permission denied") ) // ⽣成 return ErrInvalid // ⽐較 if err == os.ErrInvalid { // ... } 14 14
Sentinel Errors 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ
2. 種類の識別 ✔ 等価性により確認可能 3. スタックトレース - 固定の値のため 4. エラーの原因 - 固定の値のため 5. 追加情報 - 固定の値のため 15 15
専⽤の型 // 定義 type PathError struct { // ... }
// ⽣成 return &PathError{ ... } // ⽐較 if x, ok := err.(*os.PathError); ok { // ... } 16 16
専⽤の型 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.
種類の識別 ✔ 型アサーションの成否により 3. スタックトレース ※ 値を⽣成時に埋め込み 4. エラーの原因 ※ 値を⽣成時にラップ 5. 追加情報 ✔ 型ごとに独⾃定義 ※ error.Error のメッセージに混ぜ込めるがノイジー 17 17
特殊なチェック関数 // 定義 func IsNotExist(err error) bool { return err
== syscall.ENOENT } // もしくは func IsNotExist(err error) bool { if x, ok := err.(*customError); ok { return x.code == 123 } return false } // ⽐較 if os.IsNotExist(err) { // ... } 18 18
特殊なチェック関数 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.
種類の識別 ✔ 型アサーションの成否により 3. スタックトレース N/A エラー⽣成⽅法は任意 4. エラーの原因 N/A エラー⽣成⽅法は任意 5. 追加情報 N/A エラー⽣成⽅法は任意 19 19
ここまでは昔の知識 20 20
最近のエラー事情 Go では⻑らく エラーとはエラーメッセージ⽂字列を持っている値だ というスタンスを取ってきたが、 エラーの ラップ と 詳細情報の出⼒ の標準化のため
Go 1.13 からオプショナルなインターフェースと 標準機能が追加されることになった。 (現在でも xerrors パッケージで同等の機能が利⽤可能) 21 21
エラーがラップ可能になった ( %+v でスタックトレースも) 22 22
種類の識別をラップ対応版にする Sentinel Errors // 等価性テスト if err == os.ErrInvalid {
... } // ラップ対応版 if errors.Is(err, os.ErrInvalid) { ... } 23 23
種類の識別をラップ対応版にする 専⽤の型 // 型アサーションの成否 if x, ok := err.(*os.PathError); ok
{ ... } // ラップ対応版 var x *os.PathError if errors.As(err, &x) { ... } 24 24
各種インターフェースを実装した型 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.
種類の識別 ✔ 型アサーションの成否により 3. スタックトレース ✔ 値を⽣成時に埋め込み 4. エラーの原因 ✔ 値を⽣成時にラップ 5. 追加情報 ✔ 型ごとに独⾃定義 適切にインタフェースを実装すれば全て可能 ただし、 エラー種別ごとに実装が必要なので⾯倒 25 25
各種インターフェースを実装した型 エラー種類ごとに実装がいちいち実装が必要で ⾯倒くさい Sentinel Errors スタックトレース、エラーの原因、追加情報などを持てず エラー情報として 貧弱 26 26
⼿軽でリッチなエラーを⽣成する⽅法を考える 27 27
翻訳 if err != nil { return fmt.Errorf("database error: %v",
err) } 下位のエラーを翻訳して上位に投げるだけ → 実は何もしなくても Go 1.13 でアップデートされる 28 28
翻訳 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.
種類の識別 - その場限りの値のため 3. スタックトレース ✔ fmt.Errorf により 4. エラーの原因 ✔ %v に⼊る 5. 追加情報 - ⽂字列のみ 29 29
Sentinel Errors + fmt.Errorf // 定義 var ( ErrInvalid =
errors.New("invalid argument") ErrPermission = errors.New("permission denied") ) // ⽣成 return fmt.Errorf("error: %w", ErrInvalid) ラップしつつ新しくエラーを⽣成するため、 スタックトレースが出⼒できる 追加のメッセージが少し冗⻑ 30 30
Sentinel Errors + fmt.Errorf 観点 可否 理由 1. メッセージ ✔
error はメッセージを持つ 2. 種類の識別 ✔ errors.Is により 3. スタックトレース ✔ fmt.Errorf により 4. エラーの原因 - 複数のエラーのラップは不可 5. 追加情報 - ⽂字列のみ 31 31
もっとリッチなエラーを⼿軽に作りたい 32 32
ライブラリを作りました github.com/KoharaKazuya/errval github.com/KoharaKazuya/errbase 33 33
errval // 定義 var ( ErrInvalid = errval.Type("invalid argument") ErrPermission
= errval.Type("permission denied") ) // ⽣成 return ErrInvalid.New() return ErrInvalid.Wrap(err) // ラップする場合 // ⽐較 if xerrors.Is(err, provider.ErrInvalid) { // ... } 34 34
errval 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.
種類の識別 ✔ errors.Is により 3. スタックトレース ✔ errval により 4. エラーの原因 ✔ ErrType.Wrap により 5. 追加情報 - errval が提供しない 35 35
errbase // 定義 type MyCustomError struct { errbase.Err CustomField string
} // ⽣成 err := &MyCustomError{ CustomField: "additional information", } err.Build("failed to xxx") return err // ⽐較 var x *MyCustomError if xerrors.As(err, &x) { } 36 36
errbase 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.
種類の識別 ✔ errors.As により 3. スタックトレース ✔ errbase により 4. エラーの原因 ✔ Err.Wrap により 5. 追加情報 ✔ 独⾃の型定義が可能 37 37
⼿軽でリッチなエラー! 38 38
まとめ エラーに求める機能を整理した Go で error を⽣成する様々なパターンを紹介、⽐較した Go でエラーの⽣成をサポートするライブラリを作った github.com/KoharaKazuya/errval github.com/KoharaKazuya/errbase
39 39