Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go でのエラー生成パターン

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.

Go でのエラー生成パターン

Umeda.go 2019 Summer

Avatar for KoharaKazuya

KoharaKazuya

July 26, 2019
Tweet

More Decks by KoharaKazuya

Other Decks in Programming

Transcript

  1. 開発者が 読めることと プログラムが 読めることを 混ぜて考えない ↓ エラーの種類の識別はプログラムのため メッセージは開発者のため スタックトレースの表⽰は開発者のため エラーの原因を辿るのは開発者のため

    ※ 1 ※1 理想的にはプログラムは直接呼び出す API が定義するエラーのみ相⼿にすれば良い。 エラーを⽣成する側は原因となったエラーを「⾃分の⾔葉に翻訳して」エラーを ⽣成し直すべき。エラーの原因を辿るのは知るためで挙動を変えるためではないはず。 8 8
  2. No Handling if err != nil { return err }

    → やるべきではない 利⽤者にとって知らないエラーが出るので基本は避けるべき。 例: 「両替」ロジックの API を使ったら `permission denied` というエラーが出てきた 11 11
  3. 翻訳 if err != nil { return fmt.Errorf("database error: %v",

    err) } 下位のエラーを翻訳して上位に投げるだけ 12 12
  4. 翻訳 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.

    種類の識別 - その場限りの値のため 3. スタックトレース - fmt.Errorf により 4. エラーの原因 ✔ %v に⼊る 5. 追加情報 - ⽂字列のみ 13 13
  5. Sentinel Errors // 定義 var ( ErrInvalid = errors.New("invalid argument")

    ErrPermission = errors.New("permission denied") ) // ⽣成 return ErrInvalid // ⽐較 if err == os.ErrInvalid { // ... } 14 14
  6. Sentinel Errors 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ

    2. 種類の識別 ✔ 等価性により確認可能 3. スタックトレース - 固定の値のため 4. エラーの原因 - 固定の値のため 5. 追加情報 - 固定の値のため 15 15
  7. 専⽤の型 // 定義 type PathError struct { // ... }

    // ⽣成 return &PathError{ ... } // ⽐較 if x, ok := err.(*os.PathError); ok { // ... } 16 16
  8. 専⽤の型 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.

    種類の識別 ✔ 型アサーションの成否により 3. スタックトレース ※ 値を⽣成時に埋め込み 4. エラーの原因 ※ 値を⽣成時にラップ 5. 追加情報 ✔ 型ごとに独⾃定義 ※ error.Error のメッセージに混ぜ込めるがノイジー 17 17
  9. 特殊なチェック関数 // 定義 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
  10. 特殊なチェック関数 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.

    種類の識別 ✔ 型アサーションの成否により 3. スタックトレース N/A エラー⽣成⽅法は任意 4. エラーの原因 N/A エラー⽣成⽅法は任意 5. 追加情報 N/A エラー⽣成⽅法は任意 19 19
  11. 最近のエラー事情 Go では⻑らく エラーとはエラーメッセージ⽂字列を持っている値だ というスタンスを取ってきたが、 エラーの ラップ と 詳細情報の出⼒ の標準化のため

    Go 1.13 からオプショナルなインターフェースと 標準機能が追加されることになった。 (現在でも xerrors パッケージで同等の機能が利⽤可能) 21 21
  12. 種類の識別をラップ対応版にする Sentinel Errors // 等価性テスト if err == os.ErrInvalid {

    ... } // ラップ対応版 if errors.Is(err, os.ErrInvalid) { ... } 23 23
  13. 種類の識別をラップ対応版にする 専⽤の型 // 型アサーションの成否 if x, ok := err.(*os.PathError); ok

    { ... } // ラップ対応版 var x *os.PathError if errors.As(err, &x) { ... } 24 24
  14. 各種インターフェースを実装した型 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.

    種類の識別 ✔ 型アサーションの成否により 3. スタックトレース ✔ 値を⽣成時に埋め込み 4. エラーの原因 ✔ 値を⽣成時にラップ 5. 追加情報 ✔ 型ごとに独⾃定義 適切にインタフェースを実装すれば全て可能 ただし、 エラー種別ごとに実装が必要なので⾯倒 25 25
  15. 翻訳 if err != nil { return fmt.Errorf("database error: %v",

    err) } 下位のエラーを翻訳して上位に投げるだけ → 実は何もしなくても Go 1.13 でアップデートされる 28 28
  16. 翻訳 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.

    種類の識別 - その場限りの値のため 3. スタックトレース ✔ fmt.Errorf により 4. エラーの原因 ✔ %v に⼊る 5. 追加情報 - ⽂字列のみ 29 29
  17. Sentinel Errors + fmt.Errorf // 定義 var ( ErrInvalid =

    errors.New("invalid argument") ErrPermission = errors.New("permission denied") ) // ⽣成 return fmt.Errorf("error: %w", ErrInvalid) ラップしつつ新しくエラーを⽣成するため、 スタックトレースが出⼒できる 追加のメッセージが少し冗⻑ 30 30
  18. Sentinel Errors + fmt.Errorf 観点 可否 理由 1. メッセージ ✔

    error はメッセージを持つ 2. 種類の識別 ✔ errors.Is により 3. スタックトレース ✔ fmt.Errorf により 4. エラーの原因 - 複数のエラーのラップは不可 5. 追加情報 - ⽂字列のみ 31 31
  19. 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
  20. errval 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.

    種類の識別 ✔ errors.Is により 3. スタックトレース ✔ errval により 4. エラーの原因 ✔ ErrType.Wrap により 5. 追加情報 - errval が提供しない 35 35
  21. 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
  22. errbase 観点 可否 理由 1. メッセージ ✔ error はメッセージを持つ 2.

    種類の識別 ✔ errors.As により 3. スタックトレース ✔ errbase により 4. エラーの原因 ✔ Err.Wrap により 5. 追加情報 ✔ 独⾃の型定義が可能 37 37