複数エラーを統合する errors.Join() の設計判断レビュー

Go 1.20から追加された errors.Join() は、複数の error を一括で返却できる便利な関数です。
ただし安易に使うと、エラーの責務境界が曖昧になり、設計の見通しやデバッグ性を損なうリスクがあります。

この記事では、まず errors.Join() の技術的な概要を確認したうえで、レビューアーがチェックすべき設計観点を具体例とともに整理します。


1. errors.Join() の技術概要

複数の error を一つにまとめて返せる関数です。
結合後も errors.Is()errors.As() を使えば、個別のエラー判定が可能です。

return errors.Join(err1, err2, err3)
利用の基本方針
  • 複数の error を1つの error として返すことができる
  • 中に含まれた各エラーは errors.Is() などで検知できる
  • ただし、統合が適切かどうかの設計判断が求められる

2. 使用例と基本構造の理解

func saveAndLog(path string, logMsg string) error {
    var errs []error

    if err := saveToFile(path); err != nil {
        errs = append(errs, fmt.Errorf("save error: %w", err))
    }
    if err := writeLog(logMsg); err != nil {
        errs = append(errs, fmt.Errorf("log error: %w", err))
    }

    return errors.Join(errs...)
}
@Reviewer
`saveToFile`と`writeLog`は処理責務が異なるため、エラーをまとめて返すと呼び出し側のハンドリングが難しくなります。それぞれのエラーを個別に返すか、統合する意図を明確にするコメントを追加してください。
レビュー観点
  • 異なる責務の処理を統合している構造に注意
  • 呼び出し側で errors.Is() による個別判定が必要になる前提で設計されているか?

3. 統合によるエラーメッセージの曖昧化

save error: permission denied; log error: disk full
@Reviewer
メッセージが単純に連結されているため、処理単位ごとの失敗要因が分かりづらくなっています。ログ出力時の可読性や解析のしやすさを考慮し、出力形式や構造を見直した方が良いです。
メッセージの曖昧化に注意
  • 処理単位での失敗原因が読み取りにくい
  • ログとして機能しにくく、運用コストが増す

4. 処理構造と統合タイミングを可視化する

統合する際、どのような処理単位でどのようにエラーを収集するかを明示できると、設計の妥当性も評価しやすくなります。

UML Diagram

5. errors.Is()errors.As() を活かせるか?

以下のように統合エラーから特定の原因を取り出すことができます。

if errors.Is(err, fs.ErrPermission) {
    // アクセス拒否時の処理
}
@Reviewer
`errors.Is()` を利用するには `%w` でラップされている必要があります。`fmt.Errorf` に `%w` を使っているかどうか確認してください。
チェックポイント
  • ラップされていないエラーは後から検知できない
  • %w による明示的なラップが必須

6. レビューアー視点のチェックリスト

チェック項目 内容
統合の妥当性 統合する理由が明示されているか?責務が混在していないか?
呼び出し側対応 呼び出し側が分解・個別判定を想定した実装になっているか?
ラップ構造 %w により errors.Is() が有効になっているか?
メッセージ設計 Error() の出力が読みやすく、どの処理で発生したか判別できるか?
統合の粒度 統合対象が同一文脈内にあるか?処理境界を超えていないか?
ログとの整合性 ログ出力における視認性や検索性が損なわれていないか?

7. 過剰統合パターンの危険性

return errors.Join(validate(), save(), notify(), logAudit())
@Reviewer
このように複数処理の結果を一括で統合してしまうと、どこで失敗したかを呼び出し側が把握しづらくなります。責務ごとにエラーを分けるか、各エラーの意図を明確にする構造に見直してください。
過剰統合の問題点
  • 各処理の独立性を損なう
  • エラーの責任所在が不明確になる
  • 再利用やテストも困難になる

8. まとめ:構造的妥当性と意図説明がカギ

errors.Join() を使う場面では、便利さだけでなく構造的妥当性と設計意図の伝達性を評価対象にするべきです。

  • 同一責務・文脈内のエラー統合に限定する
  • 呼び出し側に分解・判定可能な設計を残す
  • ログ・監視文脈でも意味を維持できるよう設計する
レビューアーが見るべきこと
  • 統合が処理単位・責務単位で意味のあるまとまりになっているか?
  • 意図を満たすために errors.Join() が最適か、それとも他手段(構造体返却、個別ハンドリング)が適切か?