Menu
Coddy logo textTech

エラーラッピング (fmt.Errorf)

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 54/107。

エラーがアプリケーションの複数のレイヤーを通過する場合、エラーがどこで発生したかを知ることが非常に重要になります。Goのfmt.Errorf関数と%w動詞を使用すると、元のエラーを保持しながらコンテキストを追加してエラーをラップできます。

エラーラッピングは、エラーの連鎖を作成します。各レイヤーは、エラーが発生したときに何をしようとしていたかに関する情報を追加できます:

func ReadConfig(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("reading config file: %w", err)
    }
    // データを処理する...
    return nil
}

%w 動詞は特殊です。これは元のエラーを新しいエラーの中にラップします。これは、単にエラーを文字列に変換し、元のエラーの識別情報を失ってしまう %v とは異なります。

func LoadSettings() error {
    err := ReadConfig("settings.json")
    if err != nil {
        return fmt.Errorf("loading settings: %w", err)
    }
    return nil
}

LoadSettingsが失敗すると、エラーメッセージには完全なチェーンが表示されます:"loading settings: reading config file: open settings.json: no such file or directory"。各レイヤーがコンテキストを追加するため、デバッグが非常に容易になります。

ラップされたエラーには、依然として元のエラーが含まれています。次のレッスンでは、errors.Is()errors.As() を使用して、これらのエラーをアンラップし、中身を確認する方法を学びます。

challenge icon

チャレンジ

簡単

複数のレイヤーにわたるエラーラッピングを実証する注文処理システムを構築しましょう。各レイヤーがエラーにコンテキストを追加する関数のチェーンを作成し、問題がどこで発生したかを正確に追跡しやすくします。

コードは2つのファイルに分けて構成します:

  • orders.go: エラーが上位に伝播する際、それらをラップする複数のレイヤーを持つ注文処理ロジックを作成します。

    処理チェーンを形成する3つの関数を実装してください:

    • ValidateItem(itemID string) error - itemID が "INVALID" の場合はメッセージ "item not found" を持つエラーを返し、それ以外の場合は nil を返します。
    • ProcessOrder(orderID, itemID string) error - ValidateItem を呼び出します。エラーが返された場合は、コンテキスト "processing order [orderID]: %w" でラップします。それ以外の場合は nil を返します。
    • SubmitOrder(customerName, orderID, itemID string) error - ProcessOrder を呼び出します。エラーが返された場合は、コンテキスト "submitting order for [customerName]: %w" でラップします。それ以外の場合は nil を返します。

    各レイヤーは fmt.Errorf%w 動詞を使用して、下のレイヤーからのエラーをラップし、コンテキストのチェーンを構築する必要があります。

  • main.go: 入力から注文の詳細を読み取り、SubmitOrder を呼び出して結果を表示します。エラーが発生した場合は、完全なエラーチェーンを出力します。成功した場合は、確認メッセージを出力します。

以下の入力が提供されます:

  • 1行目: 顧客名
  • 2行目: 注文ID
  • 3行目: アイテムID

結果を出力してください:

  • エラーが発生した場合: Error: [full error chain]
  • 成功した場合: Order [orderID] submitted successfully for [customerName]

例えば、AliceORD-123INVALID が与えられた場合、出力は以下のようになります:

Error: submitting order for Alice: processing order ORD-123: item not found

エラーメッセージに完全なチェーンが表示されていることに注目してください。トップレベルの送信試行から、注文処理を経て、実際のバリデーション失敗まで問題を追跡できます。各レイヤーが %w を使用して独自のコンテキストを追加しました。

また、BobORD-456ITEM-001 が与えられた場合、出力は以下のようになります:

Order ORD-456 submitted successfully for Bob

チートシート

元のエラーを保持したままエラーをラップするには、fmt.Errorf%w 動詞を使用します。

func ReadConfig(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("reading config file: %w", err)
    }
    return nil
}

%w 動詞は、元のエラーを新しいエラーの中にラップします。これに対し、%v はエラーを文字列に変換するだけで、元のエラーの識別情報を失ってしまいます。

エラーのラップは、各レイヤーがコンテキストを追加するチェーンを作成します。

func LoadSettings() error {
    err := ReadConfig("settings.json")
    if err != nil {
        return fmt.Errorf("loading settings: %w", err)
    }
    return nil
}

結果として得られるエラーメッセージは、チェーン全体を表示します:"loading settings: reading config file: open settings.json: no such file or directory"

自分で試してみよう

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// 顧客名を読み込む
	scanner.Scan()
	customerName := scanner.Text()
	
	// 注文IDを読み込む
	scanner.Scan()
	orderID := scanner.Text()
	
	// 商品IDを読み込む
	scanner.Scan()
	itemID := scanner.Text()
	
	// TODO: 入力値を使用してSubmitOrderを呼び出す
	// TODO: エラーが発生した場合は、次のように出力する: Error: [full error chain]
	// TODO: 成功した場合は、次のように出力する: Order [orderID] submitted successfully for [customerName]
	
	_ = customerName
	_ = orderID
	_ = itemID
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン