if err != nil は“ただの構文”ではない:レビューで注視すべき設計意図

Goにおけるエラーハンドリングは、if err != nilという記述に集約される。
ただしそれは“定型文”ではなく、文脈や責務の設計を読み解くための構造的トリガーでもある。

本記事では、if err != nilの使い方を通じて、レビューアーがどのように設計意図を逆算して評価すべきかを実践的に解説していく。
良い構造例から確認し、そのあとに典型的なNG例をレビューコメント付きで解説する構成とする。


1. シンプルかつ責務が明確な理想的ハンドリング

以下の例は、構造として非常に素直であり、レビューでも好ましいとされるパターンである。

func loadUserData(userID string) error {
    user, err := repo.FindByID(userID)
    if err != nil {
        return fmt.Errorf("failed to find user: %w", err)
    }

    if err := user.LoadProfile(); err != nil {
        return fmt.Errorf("failed to load profile: %w", err)
    }

    return nil
}

このようなコードには以下の特徴がある:

  • 逐次処理とエラー分岐が明確に整理されている
  • コンテキスト付きのエラーラップにより、呼び出し元でも原因が分かりやすい
  • ログ出力が不要な設計(UI側で補足すれば良いため)

Goでは「早期return」「ラップによるエラー情報の蓄積」がセットで扱われる。命名・構造・ログポリシーに一貫性があれば、if err != nil は冗長ではなく“設計の結果”となる。


2. 指摘対象となる “曖昧な if err != nil”

次に、典型的な初学者によるNGパターンと、レビュー時に注目すべきコメント例を提示する。

if err != nil {
    // TODO: handle error
}
@Reviewer
コメントのみで実装がなく、意図不明瞭です。仮であってもログ出力やreturnなど、明示的な動作を追加してください。
if err != nil {
    // nothing
}
@Reviewer
空ブロックは原則避けてください。処理しないならreturnするか、構造ごと見直してください。

if err != nilの“中身が曖昧”な場合、その構文は設計上のノイズになる。責務が明確でない場合、レビューでは積極的に指摘すること。


3. “else” によるネストの増加

Goでは「早期returnによって構造を浅くする」という書き方が推奨されている。

if err != nil {
    return err
} else {
    next()
}
@Reviewer
`else`は不要です。早期returnで処理を分けることで、可読性の高い構造になります。

改善後の構造は以下のようにシンプルになる:

if err != nil {
    return err
}
next()

if-else構造の除去は、Goにおけるコードスタイルとしての重要な原則。レビューではネストが深くなっていないかを確認すること。


4. エラーが“ただ通過するだけ”の構造

err := step1()
if err != nil {
    return err
}
err = step2()
if err != nil {
    return err
}

%% @Reviewer エラーハンドリングの粒度が揃っていません。ログ出力がなかったり、責務単位が不明瞭だったりするため、関数分割を検討してください。

このような繰り返し構文が続く場合、「どのステップで失敗したか」がログやエラー文からは分かりにくい。
各処理単位でログ出力やエラーラップを行い、責務とログの一貫性を持たせる構造に改修することが望ましい。


5. ログとreturnの偏り・重複

if err != nil {
    log.Println("failed to send mail:", err)
    // return がない
}
@Reviewer
ログ出力のみでreturnしていないため、呼び出し元にエラーが伝わりません。責務に応じてログのみ/returnの両立可否を明示してください。

また、ログとreturnが両方あるが、それが冗長な場合も多い:

if err != nil {
    log.Println("error:", err)
    return err
}
@Reviewer
同じ内容をログとreturnで重複させていませんか?ログの対象は責務の範囲に合わせて限定的にする方が保守しやすくなります。

レビューでは「ログとreturnの分離」が重要な観点。UI層でログ出力するなら、ドメイン層でのログは削除対象となることもある。


6. 責務が混在している構造例

if err := saveUser(); err != nil {
    log.Println("save failed:", err)
    return notifyAdmin(err)
}
@Reviewer
ログ・通知・returnが1箇所に集まりすぎています。責務を分離して、ログ・通知・エラーハンドリングの境界を明確にしましょう。

1つのif err != nilの中で複数の副作用(ログ出力・通知・伝搬)を扱ってしまうと、責任の所在が不明確になる。
設計上の関心事を関数単位で切り分けることで、可読性と保守性が大きく向上する。


7. if err != nil レビュー観点チェックリスト

観点 チェック内容
処理の明示性 ブロック内部に具体的処理があるか?
ネスト構造 else や深いネストがないか?
ログ vs return 重複・不足・責務不一致がないか?
エラーラップ コンテキスト付きで返却されているか?
責務分離 ログ・通知・returnが分割されているか?

あとがき:構文を“読む”のではなく、設計を“見抜く”

if err != nilは単なる構文ではなく、エラーハンドリングという設計判断の露出点である。
レビューアーはその構文の中にある以下のような意図を読み解く必要がある。

  • どこまで責任を負うか(ログ出力・通知・伝搬の範囲)
  • どこで処理を分岐させるか(早期return vs ネスト)
  • どのレイヤーが何をすべきか(UI層 vs ドメイン層)

::: 構文の正しさだけを確認するのではなく、それが設計全体のどこを反映しているのかという視点を忘れずに。
初級者が書くif err != nilには“言葉になっていない構造の未整理”が表れることが多く、それを見抜くのがレビューアーの重要な仕事となる。 :::