🔩 ねじき教室 Go と Web の教室

エラー処理

読了目安 約3分

失敗が日常である本番のコードで、Go が例外ではなく値としてエラーを扱う理由と、if err != nil の慣習を学びます。

この章の目次

競プロの入力は問題文の制約を必ず満たすので、失敗の処理を書かずに済みました。 本番の Web サービスには、この前提がありません。

失敗するのが普通の世界

ネットワークは切れ、データベースは応答を返さず、ユーザーは年齢の欄に「二十八」と入力してきます。 これらは日常なので、本番のコードは失敗時の処理を成功時と同じ比重で書きます。

値としての error

多くの言語は失敗を例外(throw と try-catch)で表しますが、Go は普通の値で表します。 error は「失敗したかどうか、失敗したならその内容」を表す型で、失敗しうる関数が最後の戻り値として返します(成功時は nil)。

Go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	for _, s := range []string{"42", "abc", ""} {
		n, err := strconv.Atoi(s)
		if err != nil {
			fmt.Printf("%q の変換に失敗: %v\n", s, err)
			continue
		}
		fmt.Printf("%q の変換に成功: %d\n", s, n)
	}
}

strconv.Atoi は文字列を int に変換する関数で、失敗すると 2 つ目の戻り値にエラーを返します。 この「err を調べて対処する」形が、Go のエラー処理の基本です。

if err != nil という慣習

Go のコードには、この 3 行が繰り返し現れます。

Go
n, err := strconv.Atoi(s)
if err != nil {
	return err // 自分では対処できないので、呼び出し元に返す
}

冗長に見えますが、失敗の可能性がコードの見た目に現れ、例外のように処理を飛び越えて伝わることがありません。

エラーを握りつぶすとどうなるか

エラーは _ に代入すれば無視できてしまいます。

Go
n, _ := strconv.Atoi(s) // 失敗しても n = 0 のまま処理が続く

この書き方をエラーの握りつぶしと呼びます。 握りつぶした失敗はゼロ値として下流に流れ、症状は遠く離れた場所で出ます(「なぜか残高が 0 円で登録されている」)。 原因の行を探す調査に、時間を取られます。

fmt.Errorf と %w で文脈を付ける

そのまま返すだけだと、最上位に届いた「invalid syntax」からは何の変換の失敗かわかりません。 fmt.Errorf は書式付きでエラーを作る関数で、%w を使うと元のエラーを包んだまま説明を足せます。

Go
package main

import (
	"errors"
	"fmt"
	"strconv"
)

func parseAge(s string) (int, error) {
	n, err := strconv.Atoi(s)
	if err != nil {
		return 0, fmt.Errorf("年齢の解析に失敗: %w", err)
	}
	if n < 0 {
		return 0, errors.New("年齢が負の値です")
	}
	return n, nil
}

func main() {
	for _, s := range []string{"28", "-3", "abc"} {
		age, err := parseAge(s)
		if err != nil {
			fmt.Println("エラー:", err)
			continue
		}
		fmt.Println("年齢:", age)
	}
}

%w で包む(ラップする)と、外側の関数が「どの処理の途中だったか」を足し、内側のエラーが「直接の原因」を保ちます。

panic は本当に異常なときだけ

Go にも panic という、プログラムを即座に停止させる仕組みがあります。 これはプログラムのバグのためのもので、ユーザー入力の不正やネットワークの失敗を panic で扱うわけにはいきません。

1 つのプログラムが全ユーザーからのアクセスを処理しているので、1 人の入力で止めれば無関係な全ユーザーも道連れになるからです。 起こりうる失敗は、すべて error で返します。

試してみよう

上の parseAge を、次の順に書き換えてください。

  1. 151 以上の値もエラーにする(メッセージは「年齢が大きすぎます」など)。
  2. main のループに "200" を足して、追加したエラーが出ることを確かめる。
  3. 最後に、strconv.Atoi の行を n, _ := strconv.Atoi(s) に変えて、"abc" がどう扱われるかを観察する。エラーの握りつぶしを、その目で確かめられます。