メインコンテンツにスキップ
モジュール 10 · レッスン 2 / 5コース内の 47/50~14 min
モジュールのレッスン (2/5)

慣用的なエラー処理

Go のエラー処理は明示的です。例外や try/catch はありません。失敗する可能性のあるすべての関数は、最後の値として error を返し、呼び出し元が決定します。この章では、堅牢な方法でエラーを生成、伝播、および検査するための慣用的なパターンを収集します。

黄金律

Go
data, err := os.ReadFile(path)
if err != nil {
    return fmt.Errorf("config %s: %w", path, err)
}

4 つの原則:

  1. error は最後の返品です。
  2. if err != nil を使用して すぐに確認してください。
  3. 伝播する前に コンテキストを追加します (fmt.Errorf + %w を使用)。
  4. エラーは決して無視しないでください**。処理するか、記録するか、返すかのいずれかを行ってください。
Go
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)

センチネルエラー

センチネル エラー は、呼び出し元が比較できるエクスポートされたグローバル変数 var ErrXxx = errors.New("...") です。

Go
package store

var ErrNotFound = errors.New("not found")

func Get(key string) (string, error) {
    if !exists(key) {
        return "", ErrNotFound
    }
    // ...
}

呼び出し元は errors.Is (== ではありません) を使用し、ラップ チェーンをたどります。

Go
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
    // handle 404
}

標準的な例: io.EOF、sql.ErrNoRows、os.ErrNotExist。

カスタムタイプのエラー

構造化データ (HTTP コード、無効なフィールドなど) を運ぶには、タイプを定義します。

Go
type ValidationError struct {
    Field string
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}

呼び出し元は errors.As を使用して、チェーンに沿って安全にダウンキャストします。

Go
var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("invalid field:", ve.Field)
}

errors.As は、渡されたポインターに割り当て可能なエラーが見つかるまで、Unwrap() チェーンをたどります。

%w によるラッピング

fmt.Errorf と %w 動詞を組み合わせると、元のエラーを ラップして、errors.Is/errors.As 用に保持するエラーが生成されます。

Go
if err := db.Query(); err != nil {
    return fmt.Errorf("load users: %w", err)
}

%w を使用しない (%v または %s を使用する) と、より豊富な文字列が得られますが、チェーンが 失われます。呼び出し元は特定のエラーを認識できなくなります。

%w のルール:

  • fmt.Errorf 呼び出しごとに 1 つだけ (マルチラップには errors.Join が必要です)。
  • 引数は文字列ではなく、error でなければなりません。
  • コンテキストを追加します。元のメッセージを複製しないでください。
Go
// bad: duplicates
return fmt.Errorf("error: %w", err) // "error: file not found"

// good: useful context
return fmt.Errorf("load config %s: %w", path, err)

errors.Join (複数のエラーの場合)

Go 1.20 以降、errors.Join(errs...) は複数のエラーを 1 つに結合し、errors.Is/As ですべてのエラーを検出できるようにします。

Go
var errs []error
for _, item := range items {
    if err := process(item); err != nil {
        errs = append(errs, fmt.Errorf("item %s: %w", item.ID, err))
    }
}
if len(errs) > 0 {
    return errors.Join(errs...)
}

panic を使用する場合

予想されるエラーに対しては決して使用しないでください。 次の 3 つの場合のみ:

  1. 回復不可能なバグ: 「起こり得ない」箇所での不変式違反 (panic("unreachable"))。
  2. スタンドアロン プログラムの init()/main() で 初期化に失敗しました (重要な構成が欠落している → フェイル ファスト)。
  3. それを明示的に文書化するライブラリ API: 例: regexp.MustCompile (パターンが無効な場合はパニックします。パターンはビルド時の定数であるため許容されます)。

それ以外はすべて error です。 panic をショートカットとして使用しないでください。上に伝播するため、構成が破壊され、defer Close() がスキップされ、呼び出し元に負担がかかります。

演習

運動#go.m10.l2.e1
試行回数: 0読み込み中…

fmt.Error で os.ReadFile のエラーをラップし、'config:' (コンパイル パス) のコンテキスト % を実行します。

エディターを読み込み中…

3 回の試行後に解決策が利用可能になります

運動#go.m10.l2.e2
試行回数: 0読み込み中…

米国のエラーです。確認エラーです。メッセージ 'マンカ!' のエラーが見つかりませんでした。エラーが見つかりませんでした。タルカソで。

エディターを読み込み中…

3 回の試行後に解決策が利用可能になります

Quiz#go.m10.l2.e3
準備完了

Quando èidiomatico usare パニック in una libreria Go?

Go
func F(x int) {
  // ??? panic(...)
}
回答の選択肢