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

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

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

Umeda.go 2019 Summer

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