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

エラー設計について / Designing Errors

エラー設計について / Designing Errors

アプリケーションのエラーハンドリングについて

- エラーとは?
- エラーに求められること
- Goのアプリケーションでエラーをどのように扱うか?

Failure is your Domainという記事を参考にしています。
https://middlemost.com/failure-is-your-domain/

Google Presentation版
https://docs.google.com/presentation/d/1JIdZ4IVW2D3kEFUtWSvHNes3r3ykojGuUAQAnhmEVs0/edit?usp=sharing

morikuni

May 18, 2019
Tweet

More Decks by morikuni

Other Decks in Technology

Transcript

  1. エラーとは? • エラーは処理が失敗したときに発生する • エラーには既知のエラーと未知のエラーの2種類がある ◦ 既知のエラー ▪ 発生することが想定できているエラー ▪

    例: 明示的にハンドリングされているエラー ◦ 未知のエラー ▪ 発生することが想定できていないエラー ▪ 例: panic, 500 Internal Server Errorになるようなエラー • 同じエラーでも状況によって既知か未知かは異なる
  2. Goのアプリケーションでエラーをどのように扱うか? • アプリケーション固有のエラーコードを定義し、エラーを識別する ◦ HTTPとかgRPCもエラーコードによる識別をしているので従う • 既知の外部のエラーは上で定義したエラーコードに変換する • 定義したエラーコードのみを想定してエラーハンドリングをする ◦

    外部のエラーをそのまま使い回すと、想定すべきエラーが多くなり、アプリケーションのエ ラーハンドリングが複雑になる • エンドユーザーがエラーを解決できる場合にメッセージを追加する • 必要に応じてコールスタックや引数の情報を追加する • 未知のエラーが見つかったらハンドリングして既知にする
  3. github.com/morikuni/failure-example /simple-czrud • /create, /read, /update, /deleteの操作を持つ HTTPのKey-Value Store •

    Controller, Service, Database, Modelという シンプルな構成 • データベースにはMySQLを使用 failureを使ったエラーハンドリング
  4. • アプリケーション固有のエラーコードを定義する failureを使ったエラーハンドリング package errors import ( "github.com/morikuni/failure" ) const

    ( InvalidArgument failure.StringCode = "InvalidArgument" NotFound failure.StringCode = "NotFound" AlreadyExist failure.StringCode = "AlreadyExist" )
  5. • 既知の外部のエラーは定義したエラーコードに変換する ◦ failure.Translateでエラーにエラーコードを付与できる failureを使ったエラーハンドリング const query = ` SELECT

    v FROM kv WHERE k = ? ` r := db.conn.QueryRowContext(ctx, query, key) var i int64 if err := r.Scan(&i); err != nil { if err == sql.ErrNoRows { return 0, failure.Translate(err, errors.NotFound) } return 0, failure.Wrap(err) }
  6. • 定義したエラーコードのみを想定してエラーハンドリングをする ◦ failure.CodeOfでエラーコードを取り出せる failureを使ったエラーハンドリング func httpStatus(err error) int {

    switch c, _ := failure.CodeOf(err); c { case errors.InvalidArgument: return http.StatusBadRequest case errors.NotFound: return http.StatusNotFound case errors.AlreadyExist: return http.StatusConflict default: return http.StatusInternalServerError } }
  7. • 必要に応じてコールスタックや引数の情報を追加する ◦ err.Error()などが自動で生成される failureを使ったエラーハンドリング c.logger.Printf("%v\n", err) // controller.(*Controller).create: service.(*service).Create:

    Specified key already exists. Use update for existing key.: key=a: code(AlreadyExist) c.logger.Printf("%+v\n", err) // [controller.(*Controller).create] /Users/morikuni/go/src/github.com/morikuni/failure-example/simple-crud/controller/controller.go:76 // [service.(*service).Create] /Users/morikuni/go/src/github.com/morikuni/failure-example/simple-crud/service/service.go:32 // message("Specified key already exists. Use update for existing key.") // key = a // code(AlreadyExist) // [CallStack] // [service.(*service).Create] /Users/morikuni/go/src/github.com/morikuni/failure-example/simple-crud/service/service.go:32 // [controller.(*Controller).create] /Users/morikuni/go/src/github.com/morikuni/failure-example/simple-crud/controller/controller.go:74 // …(略)
  8. failureとxerrorsの考え方の違い failure xerrors エラーの比較 failure.Isによって最新のエラー コードを見る xerrors.Isによってエラーが含まれてい るか見る エラー情報の取り出し failure.MessageOfのように1

    フィールド毎に関数を用意する xerrors.Asによってオブジェクトに情報 をマッピングする エラーのラップ方法 failure.Wrapper interfaceでラッピ ング方法が統一されている ラッパー毎にxerrors.Errorfなどの専用 の関数を用意する カスタマイズ性 Wrapperは1つずつ独立して使用 可能 挙動を変えるためには、新しい型を実 装する
  9. • エラーには既知のエラーと未知のエラーがある • 未知のエラーを既知とする方法がエラーハンドリングである • 関係者によってエラーに求める情報が異なる ◦ エラーの識別情報 (for アプリケーション)

    ◦ 人間が理解可能なメッセージ (for エンドユーザー) ◦ コールスタックやコンテキスト (for 運用者) • アプリケーション内のエラーを定義し、自分達でエラーを管理しよう • github.com/morikuni/failure オススメです まとめ