osio_sioの日記

自分用メモ

GOのエラーハンドリングについて

はじめに

インターンを通して、GO(echo)を用いたAPIの実装を行ったのですが、その中でエラーハンドリングについて色々と勉強になったのでメモとして残しておきます✍️

前提の考え方

まず、前提として、エラーログを出すときは、 "(自分が追加したメッセージ) + 元のエラー" といった風に、元のエラーは必ず出したほうがいいです。これは、根本的原因が元のエラーに入っているためです。
(ちなみに、エラーに何が入っているか確認する方法としては、
・fmt.Println などで標準出力に出す
・Logger を使う
・レスポンスに含める
IDE のデバッガ機能などで変数の中身を見る
などがあります)

また、レスポンスとログに出すエラーとしては、以下のような違いがあります。
レスポンスに出すエラー:クライアント側(フロントエンドなど)の知りたい情報を返す
ログに出すエラー : 自分側(バックエンド)の知りたい情報を返す

クライアント側の知りたい情報は、例えばhttp status codeなどが挙げられます。
(ステータスコード一覧)

具体的なコード例

以上を踏まえた実際のコード例はこんな感じになりました。

if err != nil {
    c.Logger().Errorf("ログに出したいメッセージ: %v", err)
    return echo.NewHTTPError(http.StatusBadRequest, "クライアントに表示したいエラーメッセージ")
}

ref:https://echo.labstack.com/docs/error-handling ref:https://github.com/labstack/echo/blob/v4.11.4/echo.go#L887-L893

fmt.Errorfを使う場合

また、fmt.Errorfを使う場合もあります。これは、新しいエラーをフォーマットして生成するために使用されます。エラーメッセージに追加のコンテキスト情報を含めたい場合や、既存のエラーに関する情報をラップしてさらに上のレイヤーに伝播させたい場合に便利です。エラーをラップすると、errors.Isやerrors.As関数を使って後でそのエラーを検査することができます。
例えば、以下のように使うことができます。

func openFile(filename string) error {
    _, err := os.Open(filename)
    if err != nil {
        // エラーをラップして追加の情報を含めて返す
        return fmt.Errorf("ファイル '%s' のオープンに失敗しました: %w", filename, err)
    }
    return nil
}

func main() {
    err := openFile("nonexistent_file.txt")
    if err != nil {
        // os.ErrNotExistエラーかどうかをチェック
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("ファイルが存在しません。:", err)
            // ここにファイルが存在しない場合の特定の処理を書く
        } else {
            fmt.Println("予期せぬエラーが発生しました。:", err)
        }
    }
}

%wは、fmt.Errorf関数内でのみ特別な意味を持ち、エラーをラップして、エラーの詳細を保持しながら新しいエラーを生成することができます。

また、以上の内容から分かるように、errorの比較はif err == fs.ErrExist とするのではなく、errors.Isを使ってif errors.Is(err, fs.ErrExist)とするほうが、errors.Isだとエラーがラップされていても成功するため、好ましいとされています。 ref: https://pkg.go.dev/errors