1 of 27

failureでお手軽エラーハンドリング

2024/04/23 Goのエラーハンドリング 最新事情Lunch LT

morikuni

2 of 27

自己紹介

名前: morikuni

X(Twitter): @inukirom

所属: 株式会社XYZ

Go歴: 8年 (Go 1.5~)

3 of 27

Goのアプリケーションで

ほどほどに簡単で

それなりに通用する

エラーハンドリングライブラリfailureの紹介

本日の内容

4 of 27

failureを作った理由

5 of 27

エラー処理をある程度パターン化して

エラー関連のコードをシンプルしたかったから

failureを作った理由

6 of 27

エラー処理のパターン化のために

アプリケーションのエラーに求められる性質は?

7 of 27

アプリケーションのエラーに求められる性質

  1. エラーがどういうものなのか識別できること
    • ifで分岐できること
  2. エラーがどこで発生したのかわかること
    • ログから原因となるソースコードにたどり着けること
  3. エラーがどういう原因で発生したのか調査できること
    • エラーが発生したときの引数や状態が含められること

8 of 27

アプリケーションエラーの実現方法

  • 標準ライブラリのみ
  • 独自のエラー型
  • failure

9 of 27

標準ライブラリでのエラー処理

errorsとfmtパッケージのみを使う場合

10 of 27

標準ライブラリでのエラー処理

  • エラーの識別
    • var ErrX = errors.New(“error x”) => errors.Is(err, ErrX)
  • エラーの発生箇所
    • fmt.Errorf(“x failed: %w”, err)
  • エラーの原因
    • fmt.Errorf(“args = %v, %w”, args, err)

11 of 27

標準ライブラリでのエラー処理

  • エラーの識別
    • var ErrX = errors.New(“error x”) => errors.Is(err, ErrX)
  • エラーの発生箇所
    • fmt.Errorf(“x failed: %w”, err)
  • エラーの原因
    • fmt.Errorf(“args = %v, %w”, args, err)
  • 文字列なので構造化ログを吐けない
  • fmtの%dと%sの書式や順番を間違えやすい
  • 失敗した処理名などを書くのが面倒くさい

12 of 27

アプリケーション独自のエラーを設計する場合

エラーコード

type AppError struct {

ErrorCode string

StackTrace StackTrace

Args []any

}

エラー型

type UserError struct {

UserID string

}

type PermissionError struct {

Target Resource

}��

エラー値

type HTTPError int

13 of 27

アプリケーション独自のエラーを設計する場合

  • エラーの識別
    • 値での識別、型での識別、エラーコードでの識別、なんでもできる!
  • エラーの発生箇所
    • スタックトレース、テキスト、なんでもできる!
  • エラーの原因
    • エラー型のフィールドに原因を入れる、テキスト、なんでもできる!

14 of 27

アプリケーション独自のエラーを設計する場合

  • エラーの識別
    • 値での識別、型での識別、エラーコードでの識別、なんでもできる!
  • エラーの発生箇所
    • スタックトレース、テキスト、なんでもできる!
  • エラーの原因
    • エラー型のフィールドに原因を入れる、テキスト、なんでもできる!

最高!

�だけど選択肢が多すぎてどうすれば...

15 of 27

failureを使う場合

16 of 27

failureを使う場合

  • エラーの識別
    • エラーコード (+ エラー値, エラー型)
  • エラーの発生箇所
    • スタックトレース + エラーメッセージ自動生成
  • エラーの原因
    • type Context map[string]string に引数などを入れる

17 of 27

failureでのエラーの識別

type ErrCode string

const CodeBadRequest ErrCode = “BadRequest”

return failure.New(CodeBadRequest)

if failure.Is(err, CodeBadRequest) { … }

エラーコードを定義

(gRPC codes.Code等も使用可能)

エラーコードから�エラーを作成

エラーコードでエラーを識別

18 of 27

failureでのエラーの発生箇所の特定

B().Error() // main.B: main.A[CodeX]

func A() error { return failure.New(CodeX) }�func B() error { return failure.Wrap(err) }

エラー作成時に�スタックトレースを自動取得

スタックトレースから

関数名を含む�エラー文字列を自動生成

スタックトレースは�一番深いものを取り出して構造化ログなどに使える

for _, f := failure.CallStackOf(err).Frames() {� fmt.Printf(“%s:%d, ”, f.File(), f.Line())�} // main.go:7, main.go:9, ……

19 of 27

failureでのエラーの原因の特定

func ValidateSize(s string, size int) error {� if len(s) != size {� return failure.New(CodeX, failure.Context{� “s”: s,� })� }� return nil�}

ValidateSize(“a”, 3).Error()�// main.ValidateSize[CodeX]({s=a})

Error() の中に情報が入る

type Context map[string]string

エラーを返すときに�引数など、エラーの原因となった情報をContextにkey-value形式で付加

20 of 27

failureのお手軽導入手順

21 of 27

failureの導入手順 Step1. スタックトレースだけ手に入れる

  • エラーを返しているところで failure.Wrap を使う
    • return ErrX => return failure.Wrap(ErrX, failure.Context{…})
  • エラーの識別は errors.Is で行う
    • err == ErrX => errors.Is(err, ErrX)

これで err.Error() に関数名が自動で入るようになる�スタックトレースをログに出したい場合は failure.CallStackOf(err)�failure.Wrap を使うだけでもメリットはある

22 of 27

failureの導入手順 Step2. エラーコードへの置き換え(optional)

  • エラーコードを使ってエラーを返すようにする
    • return failure.Wrap(ErrX) => return failure.New(CodeX)
  • エラーの識別は failure.Is で行う
    • errors.Is(err, ErrX) => failure.Is(err, CodeX)

エラーコードを使うようになると failure.Translate(err, CodeY) �で元のエラーを残したままエラーコードを上書きしたりできるようになる

if errors.Is(err, ErrX) { return ErrY }�=> if failure.Is(err, CodeX) { return failure.Translate(err, CodeY) }

元のエラーが残る

23 of 27

failureのデメリット

24 of 27

failureのデメリット

  1. エラーを作る度にコールスタックを取得するので基本的には遅くなる
    • エラー毎に [32]uintptrruntime.Callers を呼んでいる
  2. failureのエラーコードの仕組みに乗っかると、もしfailureをやめることになったら移行が大変かもしれない

25 of 27

結論

26 of 27

アプリケーションにあったエラーを

設計できるなら独自のエラー型を作ろう

それが難しいならfailureは悪くない選択肢

(だと思う)

結論

27 of 27

採用PR エンジニア募集中

株式会社XYZ

社員数 約10人 (エンジニア 4人)

AI画像生成アプリ HOP�APIはGo、画像生成はPython�Goで生成AIの非同期実行、オートスケールさせる仕組み作りなどをしている��興味がある方はDMください!

HOP

自分の顔をAIに学習させて

いろんな画像が作れる