Menu
Coddy logo textTech

センチネルエラー

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

センチネルエラーは、特定のエラー条件を表す、事前に定義されたパッケージレベルのエラー変数です。errors.New を使ってその都度新しいエラーを作成するのとは異なり、センチネルエラーは一度だけ宣言され、コード全体で再利用されます。

センチネルエラーはパッケージレベルの変数として定義します。通常、名前は Err で始まります。

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

特定の条件が発生すると、関数は以下の定義済みエラーを返します:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

センチネルエラーの強力な点は、呼び出し側がエラー値を直接比較することで、特定のエラー条件を確認できることです。

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

Goの標準ライブラリは、センチネルエラーを広範囲に使用しています。例えば、io.EOFは入力の終了を知らせ、sql.ErrNoRowsはクエリ結果が空であることを示します。このパターンは、パッケージとそのユーザーの間で、どのようなエラーが期待されるかについて明確な規約を作成します。

しかし、エラーがラップされている場合、単純な等価性チェックは機能しなくなります。次のレッスンでは、errors.Is() がエラーチェーン全体をチェックすることで、どのようにこの問題を解決するかを学びます。

challenge icon

チャレンジ

簡単

特定の失敗条件を伝えるためにセンチネルエラーを使用する在庫管理システムを構築しましょう。呼び出し側がさまざまなシナリオを適切に処理できるように、パッケージレベルのエラー変数を定義します。

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

  • inventory.go: 一般的な失敗ケースに対して事前定義されたセンチネルエラーを持つ在庫システムを作成します。

    パッケージレベルで3つのセンチネルエラーを定義します:

    • ErrItemNotFound メッセージ: "item not found in inventory"
    • ErrOutOfStock メッセージ: "item is out of stock"
    • ErrInsufficientQuantity メッセージ: "insufficient quantity available"

    アイテム名とその数量のマップを保持する Inventory 構造体を作成します。以下のメソッドを実装してください:

    • NewInventory() *Inventory - 空の在庫を作成します
    • AddItem(name string, quantity int) - アイテムの数量を追加または更新します
    • GetQuantity(name string) (int, error) - アイテムの数量を返します。存在しない場合は ErrItemNotFound を返します
    • Purchase(name string, quantity int) error - アイテムの購入を試みます。アイテムが存在しない場合は ErrItemNotFound、現在の数量がゼロの場合は ErrOutOfStock、要求された数量が在庫を超えている場合は ErrInsufficientQuantity を返します。成功した場合は、在庫を減らして nil を返します。
  • main.go: 入力から在庫操作を読み取り、呼び出し側が直接の等価比較を使用して特定のセンチネルエラーをチェックする方法を示します。

    入力からアイテム名と購入数量を読み取ります。在庫を作成し、アイテム "laptop" を数量 5 で追加してから、購入を試みます。返されたエラーに基づいて、どのセンチネルエラーが発生したかを特定したことを示す特定のメッセージを出力します。

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

  • 1行目:購入するアイテム名
  • 2行目:購入する数量

どの特定のエラーが返されたかをチェックして、結果を処理します:

  • もし err == ErrItemNotFound なら: Error: Item '[name]' does not exist in our inventory と出力します
  • もし err == ErrOutOfStock なら: Error: Item '[name]' is currently out of stock と出力します
  • もし err == ErrInsufficientQuantity なら: Error: Cannot purchase [quantity] units of '[name]' - not enough in stock と出力します
  • 成功した場合: Successfully purchased [quantity] units of '[name]' と出力します

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

Successfully purchased 3 units of 'laptop'

また、 phone2 が与えられた場合、出力は以下のようになります:

Error: Item 'phone' does not exist in our inventory

また、 laptop10 が与えられた場合、出力は以下のようになります:

Error: Cannot purchase 10 units of 'laptop' - not enough in stock

チートシート

センチネルエラーとは、特定のエラー条件を表す、事前に定義されたパッケージレベルのエラー変数です。センチネルエラーは一度宣言され、コード全体で再利用されます。

センチネルエラーはパッケージレベルの変数として定義し、通常は名前を Err で始めます:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

関数は、特定の条件が発生したときに、これらの定義済みエラーを返します:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

呼び出し側は、エラー値を直接比較することで、特定のエラー条件をチェックできます:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

Goの標準ライブラリでは、io.EOFsql.ErrNoRows など、センチネルエラーが広く使用されています。

自分で試してみよう

package main

import (
	"fmt"
)

func main() {
	// 入力を読み込む
	var itemName string
	var quantity int
	fmt.Scanln(&itemName)
	fmt.Scanln(&quantity)

	// インベントリを作成し、数量5で "laptop" を追加する
	inv := NewInventory()
	inv.AddItem("laptop", 5)

	// TODO: アイテムの購入を試みる
	// inv.Purchase(itemName, quantity) を呼び出す

	// TODO: 直接比較を使用して、どの特定のセンチネルエラーが返されたかを確認する
	// if/else if 文を使用して以下を確認する:
	// - err == ErrItemNotFound
	// - err == ErrOutOfStock
	// - err == ErrInsufficientQuantity
	// - err == nil (成功)

	// TODO: エラーに基づいて適切なメッセージを出力する
	// ErrItemNotFound の場合: fmt.Printf("Error: Item '%s' does not exist in our inventory\n", itemName)
	// ErrOutOfStock の場合: fmt.Printf("Error: Item '%s' is currently out of stock\n", itemName)
	// ErrInsufficientQuantity の場合: fmt.Printf("Error: Cannot purchase %d units of '%s' - not enough in stock\n", quantity, itemName)
	// 成功の場合: fmt.Printf("Successfully purchased %d units of '%s'\n", quantity, itemName)
}
quiz icon腕試し

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

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