序章

Goの関数が失敗すると、関数はを使用して値を返します。 error 呼び出し元がその障害を処理できるようにするためのインターフェース。 多くの場合、開発者はfmtパッケージのfmt.Errorf関数を使用してこれらの値を返します。 ただし、Go 1.13より前では、この関数を使用することの欠点は、エラーが返される原因となった可能性のあるエラーに関する情報が失われることです。 これを解決するために、開発者はパッケージを使用してエラーを他のエラー内に「ラップ」する方法を提供するか、またはを実装してカスタムエラーを作成します。 Error() string それらの1つの方法 struct エラーの種類。 これらを作成するのは面倒な場合があります struct ただし、呼び出し元が明示的に処理する必要のないエラーが多数ある場合はタイプします。そのため、Go 1.13では、言語によってこれらのケースの処理を容易にする機能が追加されました。

1つの機能は、を使用してエラーをラップする機能です。 fmt.Errorf で機能する error ラップされたエラーにアクセスするために後でラップを解除できる値。 これにより、エラーラッピング機能がGo標準ライブラリに組み込まれるため、サードパーティのライブラリを使用する必要がなくなります。

さらに、関数errors.Isおよびerrors.Asを使用すると、特定のエラーが特定のエラー内のどこかにラップされているかどうかを簡単に判断でき、その特定のエラーにアクセスできます。すべてのエラーを自分でアンラップする必要なしに直接。

このチュートリアルでは、これらの関数を使用して、関数から返されるエラーに追加情報を含めるプログラムを作成してから、独自のカスタムエラーを作成します。 struct ラッピングおよびアンラッピング機能をサポートします。

前提条件

このチュートリアルに従うには、次のものが必要です。

  • バージョン1.13以降をインストールしてください。 これを設定するには、オペレーティングシステムのGoチュートリアルをインストールする方法に従ってください。
  • (オプション)Go でのエラー処理を読むことは、エラー処理のより詳細な説明のためにこのチュートリアルで役立つかもしれませんが、このチュートリアルは、より高いレベルで同じトピックのいくつかもカバーします。
  • (オプション)このチュートリアルは、 Go チュートリアルでのカスタムエラーの作成を拡張し、元のチュートリアル以降にGoに追加された機能を備えています。 前のチュートリアルを読むことは役に立ちますが、厳密には必須ではありません。

Goでのエラーの返却と処理

プログラムでエラーが発生した場合は、ユーザーに表示されないようにそれらのエラーを処理することをお勧めしますが、エラーを処理するには、最初にそれらについて知る必要があります。 Goでは、特別な機能を使用して関数からエラーに関する情報を返すことにより、プログラムのエラーを処理できます。 interface タイプ、 error インターフェース。 を使用して error インターフェイスを使用すると、任意のGoタイプを error そのタイプが Error() string メソッドが定義されました。 Go標準ライブラリは、作成する機能を提供します errorfmt.Errorf関数など、これらの戻り値の場合。

このセクションでは、を使用する関数を使用してプログラムを作成します fmt.Errorf エラーを返すには、関数が返す可能性のあるエラーをチェックするためのエラーハンドラーも追加します。 (Goでのエラー処理の詳細については、チュートリアル Go でのエラー処理を参照してください。)

多くの開発者は、現在のプロジェクトを保持するためのディレクトリを持っています。 このチュートリアルでは、という名前のディレクトリを使用します projects.

まず、 projects ディレクトリとそれに移動します:

  1. mkdir projects
  2. cd projects

から projects ディレクトリ、新しいを作成します errtutorial 新しいプログラムを保持するディレクトリ:

  1. mkdir errtutorial

次に、次のコマンドを使用して新しいディレクトリに移動します。 cd 指図:

  1. cd errtutorial

あなたが入ったら errtutorial ディレクトリ、を使用します go mod init 名前の付いた新しいモジュールを作成するコマンド errtutorial:

  1. go mod init errtutorial

Goモジュールを作成したら、という名前のファイルを開きます main.go の中に errtutorial ディレクトリを使用して nano、またはお気に入りのエディター:

  1. nano main.go

次に、プログラムを作成します。 プログラムは数字をループします 1 終えた 3 と呼ばれる関数を使用して、それらの数値が有効かどうかを判断してみてください validateValue. 番号が無効であると判断された場合、プログラムは fmt.Errorf を生成する関数 error 関数から返される値。 The fmt.Errorf 関数を使用すると、 error エラーメッセージが関数に提供するメッセージである値。 と同様に機能します fmt.Printf、ただし、メッセージを画面に出力する代わりに、メッセージを次のように返します。 error 代わりは。

次に、 main 関数の場合、エラー値がチェックされ、それが nil 値かどうか。 それが nil 値、関数が成功し、 valid! メッセージが印刷されます。 そうでない場合は、受信したエラーが代わりに出力されます。

プログラムを開始するには、次のコードをに追加します main.go ファイル:

projects / errtutorial / main.go
package main

import (
	"fmt"
)

func validateValue(number int) error {
	if number == 1 {
		return fmt.Errorf("that's odd")
	} else if number == 2 {
		return fmt.Errorf("uh oh")
	}
	return nil
}

func main() {
	for num := 1; num <= 3; num++ {
		fmt.Printf("validating %d... ", num)
		err := validateValue(num)
		if err != nil {
			fmt.Println("there was an error:", err)
		} else {
			fmt.Println("valid!")
		}
	}
}

The validateValue プログラムの関数は数値を受け取り、 error 有効な値であると判断されたかどうかに基づきます。 このプログラムでは、番号 1 は無効であり、エラーを返します that's odd. 数字 2 は無効であり、エラーを返します uh oh. The validateValue 関数はを使用します fmt.Errorf を生成する関数 error 返される値。 The fmt.Errorf 関数は、次のようなフォーマットを使用してエラーメッセージをフォーマットできるため、エラーを返すのに便利です。 fmt.Printf また fmt.Sprintf その後、それを渡す必要はありません stringerrors.New.

の中に main 関数、 for ループは、からの各番号を繰り返すことから始まります 13 値をに保存します num 変数。 ループ本体の内側で、 fmt.Printf プログラムが現在検証している番号を出力します。 次に、 validateValue 関数とパスイン num、検証中の現在の番号、およびエラー結果をに保存します err 変数。 最後に、 err ではありません nil これは、検証中にエラーが発生したことを意味し、エラーメッセージは fmt.Println. The else エラーチェックの句が出力されます "valid!" エラーが発生しなかったとき。

変更を保存した後、を使用してプログラムを実行します go run でコマンド main.go からの議論として errtutorial ディレクトリ:

  1. go run main.go

プログラムの実行からの出力は、検証が各番号と番号に対して実行されたことを示します 1 と番号 2 適切なエラーを返しました:

Output
validating 1... there was an error: that's odd validating 2... there was an error: uh oh validating 3... valid!

プログラムからの出力を見ると、プログラムが3つの数値すべてを検証しようとしていることがわかります。 初めてそれが言う validateValue 関数が返した that's odd エラー。これは、 1. 次の値、 2、エラーが返されたことも示していますが、 uh oh 今回はエラー。 最後に、 3 値が返されます nil エラー値は、エラーがなく、数値が有効であることを意味します。 方法 validateValue 関数が書かれている、 nil エラー値は、どちらでもない値に対して返されます 1 また 2.

このセクションでは、 fmt.Errorf 作成する error 関数から返した値。 また、エラーハンドラーを追加して、エラーメッセージが表示されたときに出力します。 error 関数から返されます。 ただし、エラーが発生したことだけでなく、エラーの意味を知ることが役立つ場合もあります。 次のセクションでは、特定のケースに合わせてエラー処理をカスタマイズする方法を学習します。

Sentinelエラーを使用した特定のエラーの処理

あなたが受け取るとき error 関数からの値、最も基本的なエラー処理は、 error 値は nil か否か。 これにより、関数にエラーがあったかどうかがわかりますが、特定のエラーケースに合わせてエラー処理をカスタマイズしたい場合があります。 たとえば、リモートサーバーに接続しているコードがあり、返されるエラー情報は「エラーが発生した」だけであるとします。 エラーの原因がサーバーを利用できなかったのか、接続資格情報が無効だったのかを確認することをお勧めします。 エラーがユーザーの資格情報が間違っていることを意味していることがわかっている場合は、すぐにユーザーに通知することをお勧めします。 ただし、エラーがサーバーを利用できないことを意味する場合は、ユーザーに通知する前に、再接続を数回試行することをお勧めします。 これらのエラーの違いを判断することで、より堅牢でユーザーフレンドリーなプログラムを作成できます。

特定のタイプのエラーをチェックする1つの方法は、 Error のメソッド error タイプしてエラーからメッセージを取得し、その値を探しているエラーのタイプと比較します。 プログラムで、以外のメッセージを表示したいとします。 there was an error: uh oh エラー値が uh oh. このケースを処理するための1つのアプローチは、から返された値を確認することです。 Error そのような方法:

if err.Error() == "uh oh" {
	// Handle 'uh oh' error.
	fmt.Println("oh no!")
}

の文字列値を確認する err.Error() それが価値があるかどうかを確認する uh oh、上記のコードのように、この場合は機能します。 しかし、次の場合、コードは機能しません uh oh エラー string プログラムの他の場所では少し異なります。 この方法でエラーをチェックすると、エラーがチェックされるすべての場所を更新する必要があるため、エラーのメッセージ自体を更新する必要がある場合にも、コードが大幅に更新される可能性があります。 たとえば、次のコードを考えてみましょう。

func giveMeError() error {
	return fmt.Errorf("uh h")
}

err := giveMeError()
if err.Error() == "uh h" {
	// "uh h" error code
}

このコードでは、エラーメッセージにタイプミスが含まれており、 ouh oh. これに気づき、ある時点で修正されたが、いくつかの場所でこのエラーチェックを追加した後でのみ、それらすべての場所でチェックを更新する必要があります。 err.Error() == "uh oh". 1つが欠落している場合、これは1文字の変更のみであるため簡単ですが、予期されているカスタムエラーハンドラーは、予期されているため実行されません。 uh h ではなく uh oh.

このような場合、特定のエラーを他のエラーとは異なる方法で処理したい場合は、エラー値を保持することを目的とした変数を作成するのが一般的です。 このようにして、コードは文字列ではなくその変数をチェックできます。 通常、これらの変数は次のいずれかで始まります err また Err それらの名前に、それらがエラーであることを示します。 エラーが定義されているパッケージ内でのみ使用されることを意図している場合は、 err プレフィックス。 エラーが他の場所で使用されることを意図している場合は、代わりに Err 関数や struct.

ここで、前のタイプミスの例でこれらのエラー値の1つを使用していたとしましょう。

var errUhOh = fmt.Errorf("uh h")

func giveMeError() error {
	return errUhOh
}

err := giveMeError()
if err == errUhOh {
	// "uh oh" error code
}

この例では、変数 errUhOh 「uhoh」エラーのエラー値として定義されます(スペルが間違っている場合でも)。 The giveMeError 関数はの値を返します errUhOh 「ええと」エラーが発生したことを発信者に知らせたいからです。 次に、エラー処理コードは err から返される値 giveMeError に対して errUhOh 「ええと」エラーが発生したかどうかを確認します。 タイプミスが見つかって修正された場合でも、エラーチェックは次の値をチェックしているため、すべてのコードは引き続き機能します。 errUhOh、およびの値 errUhOh は、エラー値の修正バージョンです。 giveMeError 戻ってきました。

この方法でチェックおよび比較することを目的としたエラー値は、センチネルエラーと呼ばれます。 センチネルエラーは、特定の意味で常に比較できる一意の値になるように設計されたエラーです。 The errUhOh 上記の値は常に同じ意味を持ち、「uh oh」エラーが発生したため、プログラムはエラーを次のように比較することに依存できます。 errUhOh そのエラーが発生したかどうかを判断します。

Go標準ライブラリは、Goプログラムの開発時に利用できるいくつかのセンチネルエラーも定義しています。 1つの例は、sql.ErrNoRowsエラーです。 The sql.ErrNoRows データベースクエリが結果を返さない場合はエラーが返されるため、接続エラーとは異なる方法でエラーを処理できます。 これはセンチネルエラーであるため、エラーチェックコードと比較して、クエリが行を返さない場合を知ることができ、プログラムは他のエラーとは異なる方法でそれを処理できます。

一般に、センチネルエラー値を作成する場合、errorsパッケージのerrors.New関数が代わりに使用されます。 fmt.Errorf これまで使用してきた関数。 使用する errors.New それ以外の fmt.Errorf ただし、エラーの動作に基本的な変更は加えられず、ほとんどの場合、両方の関数を互換的に使用できます。 2つの最大の違いは errors.New 関数は静的メッセージと fmt.Errorf 関数を使用すると、次のような値で文字列をフォーマットできます。 fmt.Printf また fmt.Sprintf. センチネルエラーは値が変化しない基本的なエラーであるため、使用するのが一般的です errors.New それらを作成します。

次に、プログラムを更新して、「uhoh」エラーの代わりにセンチネルエラーを使用するようにします。 fmt.Errorf.

まず、 main.go 新しいファイルを追加します errUhOh 歩哨エラーとそれを使用するようにプログラムを更新します。 The validateValue 関数が更新され、使用する代わりにセンチネルエラーが返されるようになりました fmt.Errorf. The main 関数が更新され、 errUhOh 歩哨エラーと印刷 oh no! 代わりにそれに遭遇したとき there was an error: 他のエラーに対して表示されるメッセージ。

projects / errtutorial / main.go
package main

import (
	"errors"
	"fmt"
)

var (
	errUhOh = errors.New("uh oh")
)

func validateValue(number int) error {
	if number == 1 {
		return fmt.Errorf("that's odd")
	} else if number == 2 {
		return errUhOh
	}
	return nil
}

func main() {
	for num := 1; num <= 3; num++ {
		fmt.Printf("validating %d... ", num)
		err := validateValue(num)
		if err == errUhOh {
			fmt.Println("oh no!")
		} else if err != nil {
			fmt.Println("there was an error:", err)
		} else {
			fmt.Println("valid!")
		}
	}
}

次に、コードを保存して使用します go run プログラムを再度実行するには:

  1. go run main.go

今回の出力には、の一般的なエラー出力が表示されます。 1 値ですが、カスタムを使用します oh no! それが見たときのメッセージ errUhOh から返されたエラー validateValue 為に 2:

Output
validating 1... there was an error: that's odd validating 2... oh no! validating 3... valid!

エラーチェック内でセンチネルエラーを使用すると、特別なエラーケースの処理が容易になります。 たとえば、 io.EOF センチネルエラーによって示されるファイルの終わりに到達したために、読み取っているファイルが失敗しているかどうか、または一部のファイルが失敗しているかどうかを判断するのに役立ちます。他の理由。

このセクションでは、以下を使用してセンチネルエラーを使用するGoプログラムを作成しました errors.New 特定のタイプのエラーがいつ発生したかを示します。 ただし、プログラムが大きくなるにつれて、エラーに含まれる情報だけでなく、より多くの情報が必要になる場合があります。 uh oh エラー値。 このエラー値は、エラーが発生した場所や発生した理由に関するコンテキストを提供するものではなく、大規模なプログラムでエラーの詳細を追跡するのは難しい場合があります。 トラブルシューティングを支援し、デバッグの時間を短縮するために、エラーラッピングを利用して必要な詳細を含めることができます。

ラッピングおよびアンラッピングエラー

エラーをラップするということは、ラップされたギフトのように、あるエラー値を取得し、その中に別のエラー値を入れることを意味します。 ただし、包装されたギフトと同様に、中身を知るには包装を解く必要があります。 エラーをラップすると、元のエラー値を失うことなく、エラーの発生場所や発生方法に関する追加情報を含めることができます。これは、エラーがラッパー内にあるためです。

Go 1.13より前では、元のエラーを含むカスタムエラー値を作成できたため、エラーをラップすることができました。 ただし、独自のラッパーを作成するか、すでに作業を行っているライブラリを使用する必要があります。 ただし、Go 1.13では、 errors.Unwrap関数と %w 動詞 fmt.Errorf 関数。 このセクションでは、プログラムを更新して、 %w エラーをより多くの情報でラップする動詞、そしてあなたはそれから使用します errors.Unwrap ラップされた情報を取得します。

エラーのラッピング fmt.Errorf

エラーをラップおよびアンラップするときに調べる最初の機能は、既存の機能への追加です fmt.Errorf 関数。 過去に、 fmt.Errorf 次のような動詞を使用して追加情報を含むフォーマットされたエラーメッセージを作成するために使用されました %s 文字列と %v 一般的な値の場合。 Go 1.13は、特別な場合の新しい動詞を追加しました。 %w 動詞。 いつ %w 動詞はフォーマット文字列に含まれ、 error 値に提供され、から返されるエラー fmt.Errorf の値が含まれます error 作成中のエラーにラップされています。

今、開きます main.go ファイルを更新して、という新しい関数を含めるようにします runValidation. この関数は、現在検証されている番号を取得し、その番号に対して必要な検証を実行します。 この場合、実行する必要があるのは validateValue 関数。 値の検証でエラーが発生した場合は、次を使用してエラーをラップします。 fmt.Errorf そしてその %w あったことを示す動詞 run error それが発生した場合は、その新しいエラーを返します。 また、更新する必要があります main 呼び出す代わりに関数 validateValue 直接呼び出す runValidation 代わりは:

projects / errtutorial / main.go

...

var (
	errUhOh = errors.New("uh oh")
)

func runValidation(number int) error {
	err := validateValue(number)
	if err != nil {
		return fmt.Errorf("run error: %w", err)
	}
	return nil
}

...

func main() {
	for num := 1; num <= 3; num++ {
		fmt.Printf("validating %d... ", num)
		err := runValidation(num)
		if err == errUhOh {
			fmt.Println("oh no!")
		} else if err != nil {
			fmt.Println("there was an error:", err)
		} else {
			fmt.Println("valid!")
		}
	}
}

更新を保存したら、を使用して更新されたプログラムを実行します go run:

  1. go run main.go

出力は次のようになります。

Output
validating 1... there was an error: run error: that's odd validating 2... there was an error: run error: uh oh validating 3... valid!

この出力で確認することがいくつかあります。 まず、値のエラーメッセージが出力されます 1 今含まれています run error: that's odd エラーメッセージで。 これは、エラーがによってラップされたことを示しています runValidationfmt.Errorf ラップされているエラーの値は、 that's odd、はエラーメッセージに含まれています。

次に、しかし、問題があります。 のために追加された特別なエラー処理 errUhOh エラーは実行されていません。 を検証する行を見ると 2 入力すると、デフォルトのエラーメッセージが表示されます。 there was an error: run error: uh oh 期待される代わりに oh no! メッセージ。 あなたは知っています validateValue 関数はまだ uh oh ラップされたエラーの最後に表示されるため、エラーが発生しますが、 errUhOh 動作しなくなりました。 これは、によって返されるエラーが原因で発生します runValidation もうありません errUhOh、それはによって作成されたラップされたエラーです fmt.Errorf. いつ if ステートメントは比較しようとします err に可変 errUhOh、falseを返します。 err と等しくない errUhOh これ以上、wrappingのエラーと同じです。 errUhOh. 修正するには errUhOh エラーチェックでは、ラッパー内からエラーを取得する必要があります。 errors.Unwrap 関数。

でエラーをアンラップ errors.Unwrap

に加えて %w Go 1.13で動詞が追加され、Go errorsパッケージにいくつかの新しい関数が追加されました。 これらの1つ、 errors.Unwrap 関数、取る error パラメータとして、渡されたエラーがエラーラッパーの場合、ラップされたエラーが返されます error. の場合 error ラッパーでない場合、関数は nil.

今、開きます main.go 再度ファイルし、を使用して errors.Unwrap、更新します errUhOh エラーチェックは、次の場合を処理します errUhOh エラーラッパー内にラップされています:

projects / errtutorial / main.go
func main() {
	for num := 1; num <= 3; num++ {
		fmt.Printf("validating %d... ", num)
		err := runValidation(num)
		if err == errUhOh || errors.Unwrap(err) == errUhOh {
			fmt.Println("oh no!")
		} else if err != nil {
			fmt.Println("there was an error:", err)
		} else {
			fmt.Println("valid!")
		}
	}
}

編集内容を保存した後、プログラムを再度実行します。

  1. go run main.go

出力は次のようになります。

Output
validating 1... there was an error: run error: that's odd validating 2... oh no! validating 3... valid!

これで、出力に次のように表示されます。 oh no! のエラー処理 2 入力値が戻ってきました。 追加 errors.Unwrap 追加した関数呼び出し if ステートメントはそれが検出することを可能にします errUhOh 両方の場合 err それ自体は errUhOh 値と同様に err 直接ラッピングしているエラーです errUhOh.

このセクションでは、 %w 動詞が追加されました fmt.Errorf ラップする errUhOh 別のエラー内のエラーとそれに追加情報を与えます。 次に、 errors.Unwrap アクセスするには errorUhOh 別のエラーにラップされているエラー。 他のエラーの中にエラーを含める string 値はエラーメッセージを読む人間にとっては問題ありませんが、HTTPリクエストエラーのステータスコードなど、プログラムがエラーを処理するのを支援するために、エラーラッパーに追加情報を含めたい場合があります。 これが発生した場合、返す新しいカスタムエラーを作成できます。

カスタムラップエラー

Goの唯一のルールは error インターフェースはそれが含まれているということです Error メソッドでは、多くのGoタイプをカスタムエラーに変えることができます。 1つの方法は、 struct エラーに関する追加情報を入力し、さらに Error 方法。

検証エラーの場合、どの値が実際にエラーを引き起こしたかを知ることが役立つ場合があります。 次に、新しいものを作成しましょう ValueError のフィールドを含む構造体 Value エラーと Err 実際の検証エラーを含むフィールド。 カスタムエラータイプは通常、 Error タイプ名の末尾にある接尾辞は、タイプ名がに準拠するタイプであることを示します。 error インターフェース。

あなたの main.go ファイルを作成し、新しい ValueError エラー構造体、および newValueError エラーのインスタンスを作成する関数。 また、というメソッドを作成する必要があります Error 為に ValueError したがって、構造体は error. これ Error メソッドは、エラーが文字列に変換されるたびに表示する値を返す必要があります。 この場合、それは使用します fmt.Sprintf を示す文字列を返す value error: 次に、ラップされたエラー。 また、更新します validateValue 関数なので、基本的なエラーだけを返すのではなく、 newValueError カスタムエラーを返す関数:

projects / errtutorial / main.go

...

var (
	errUhOh = fmt.Errorf("uh oh")
)

type ValueError struct {
	Value int
	Err   error
}

func newValueError(value int, err error) *ValueError {
	return &ValueError{
		Value: value,
		Err:   err,
	}
}

func (ve *ValueError) Error() string {
	return fmt.Sprintf("value error: %s", ve.Err)
}

...

func validateValue(number int) error {
	if number == 1 {
		return newValueError(number, fmt.Errorf("that's odd"))
	} else if number == 2 {
		return newValueError(number, errUhOh)
	}
	return nil
}

...

更新が保存されたら、でプログラムを再実行します go run:

  1. go run main.go

出力は次のようになります。

Output
validating 1... there was an error: run error: value error: that's odd validating 2... there was an error: run error: value error: uh oh validating 3... valid!

出力には、エラーが内部にラップされていることが表示されます。 ValueError によって value error: 出力でそれらの前に。 しかし uh oh エラー検出は、次の理由で再び壊れます。 errUhOh ラッパーの2つのレイヤーの中にあります。 ValueError そしてその fmt.Errorf からのラッパー runValidation. コードコードは errors.Unwrap エラーが発生すると、最初の結果になります errors.Unwrap(err) 今は *ValueError ではなく errUhOh.

これを修正する1つの方法は、 errUhOh チェックして、を呼び出すエラーチェックを追加します errors.Unwrap() 両方のレイヤーをアンラップするために2回。 これを追加するには、 main.go ファイルを作成して更新します main この変更を含める関数:

projects / errtutorial / main.go

...

func main() {
	for num := 1; num <= 3; num++ {
		fmt.Printf("validating %d... ", num)
		err := runValidation(num)
		if err == errUhOh ||
			errors.Unwrap(err) == errUhOh ||
			errors.Unwrap(errors.Unwrap(err)) == errUhOh {
			fmt.Println("oh no!")
		} else if err != nil {
			fmt.Println("there was an error:", err)
		} else {
			fmt.Println("valid!")
		}
	}
}

今、あなたの main.go ファイルと使用 go run プログラムを再度実行するには:

  1. go run main.go

出力は次のようになります。

Output
validating 1... there was an error: run error: value error: that's odd validating 2... there was an error: run error: value error: uh oh validating 3... valid!

あなたはそれを見るでしょう、ええと、 errUhOh 特別なエラー処理はまだ機能していません。 を検証する行 2 特別なエラー処理が見られると予想される入力 oh no! 出力はまだデフォルトを示しています there was an error: run error: ... エラー出力。 これは、 errors.Unwrap 関数は、ラップを解除する方法を知りません ValueError カスタムエラータイプ。 カスタムエラーをアンラップするには、独自のエラーが必要です。 Unwrap 内部エラーをとして返すメソッド error 価値。 を使用してエラーを作成する場合 fmt.Errorf とともに %w 以前の動詞、Goは実際にすでにエラーを作成していました Unwrap メソッドが追加されたので、自分で行う必要はありませんでした。 ただし、独自のカスタム関数を使用しているので、独自の関数を追加する必要があります。

最終的に修正するには errUhOh エラーケース、開く main.go を追加します Unwrap 方法 ValueError それは Err、内側にラップされたエラーが格納されているフィールド:

projects / errtutorial / main.go

...

func (ve *ValueError) Error() string {
	return fmt.Sprintf("value error: %s", ve.Err)
}

func (ve *ValueError) Unwrap() error {
	return ve.Err
}

...

次に、新しいものを保存したら Unwrap メソッド、プログラムを実行します。

  1. go run main.go

出力は次のようになります。

Output
validating 1... there was an error: run error: value error: that's odd validating 2... oh no! validating 3... valid!

出力は oh no! のエラー処理 errUhOh エラーが再び機能しているのは errors.Unwrap アンラップもできるようになりました ValueError.

このセクションでは、新しいカスタムを作成しました ValueError エラーメッセージの一部として、検証プロセスに関する情報を自分自身またはユーザーに提供するためのエラー。 エラーアンラップのサポートも追加しました ValueError それで errors.Unwrap ラップされたエラーにアクセスするために使用できます。

ただし、エラー処理は少し不格好になり、保守が難しくなっています。 ラッピングの新しいレイヤーがあるたびに、別のレイヤーを追加する必要がありました errors.Unwrap それを処理するためのエラーチェックに。 ありがたいことに、 errors.Iserrors.As の機能 errors ラップされたエラーの処理を容易にするパッケージが利用可能です。

ラップされたエラーの処理

新しいものを追加する必要がある場合 errors.Unwrap プログラムをラップするエラーの潜在的なレイヤーごとに関数呼び出しを行うと、非常に長くなり、保守が困難になります。 このため、2つの追加機能も追加されました。 errors Go1.13リリースのパッケージ。 これらの関数はどちらも、エラーが他のエラーにどれほど深く含まれていても、エラーを操作できるようにすることで、エラーの処理を容易にします。 The errors.Is 関数を使用すると、特定のセンチネルエラー値がラップされたエラー内のどこかにあるかどうかを確認できます。 The errors.As 関数を使用すると、ラップされたエラー内の任意の場所で特定のタイプのエラーへの参照を取得できます。

でエラー値をチェックする errors.Is

使用する errors.Is 特定のエラーをチェックするには、 errUhOh 手動で行っていたネストされたエラーのアンラップをすべて処理するため、特別なエラー処理ははるかに短くなります。 関数は2つかかります error パラメータ。最初のパラメータは実際に受け取ったエラーで、2番目のパラメータはチェックしたいエラーです。

クリーンアップするには errUhOh エラー処理、開いて main.go ファイルを更新し、 errUhOh チェックイン main 使用する関数 errors.Is 代わりは:

projects / errtutorial / main.go

...

func main() {
	for num := 1; num <= 3; num++ {
		fmt.Printf("validating %d... ", num)
		err := runValidation(num)
		if errors.Is(err, errUhOh) {
			fmt.Println("oh no!")
		} else if err != nil {
			fmt.Println("there was an error:", err)
		} else {
			fmt.Println("valid!")
		}
	}
}

次に、コードを保存し、を使用してプログラムを再実行します go run:

  1. go run main.go

出力は次のようになります。

Output
validating 1... there was an error: run error: value error: that's odd validating 2... oh no! validating 3... valid!

出力は oh no! エラーメッセージ。これは、エラーチェックが1つしかない場合でも errUhOh、それはまだエラーチェーンで見つかります。 errors.Is エラータイプを利用します Unwrap 探しているエラー値、センチネルエラーが見つかるか、エラーが発生するまで、エラーのチェーンを深く掘り下げ続ける方法。 Unwrap を返すメソッド nil 価値。

使用する errors.Is エラーラッピングがGoの機能として存在するため、特定のエラーをチェックするための推奨される方法です。 独自のエラー値に使用できるだけでなく、次のような他のエラー値にも使用できます。 sql.ErrNoRows このチュートリアルで前述したエラー。

でエラータイプを取得する errors.As

に追加された最後の関数 errors Go1.13のパッケージは errors.As 関数。 この関数は、特定のタイプのエラーへの参照を取得して、それとより詳細に対話する場合に使用されます。 たとえば、 ValueError 以前に追加したカスタムエラーは、で検証されている実際の値へのアクセスを提供します Value エラーのフィールドですが、最初にそのエラーへの参照がある場合にのみアクセスできます。 これはどこです errors.As 入って来る。 あなたは与えることができます errors.As 次のようなエラー errors.Is、およびエラーのタイプの変数。 次に、エラーチェーンを調べて、ラップされたエラーのいずれかが提供されたタイプと一致するかどうかを確認します。 一致する場合、エラータイプに渡された変数はエラーで設定されます errors.As が見つかり、関数は true. 一致するエラータイプがない場合は、 false.

使用する errors.As これで、 ValueError 入力して、エラーハンドラに追加のエラー情報を表示します。 あなたの main.go 最後にもう一度ファイルして、 main 新しいエラー処理ケースを追加する関数 ValueError-出力するタイプエラー value error、無効な番号、および検証エラー:

projects / errtutorial / main.go

...

func main() {
	for num := 1; num <= 3; num++ {
		fmt.Printf("validating %d... ", num)
		err := runValidation(num)

		var valueErr *ValueError
		if errors.Is(err, errUhOh) {
			fmt.Println("oh no!")
		} else if errors.As(err, &valueErr) {
			fmt.Printf("value error (%d): %v\n", valueErr.Value, valueErr.Err)
		} else if err != nil {
			fmt.Println("there was an error:", err)
		} else {
			fmt.Println("valid!")
		}
	}
}

上記のコードで、新しい宣言をしました valueErr 変数および使用 errors.As への参照を取得するには ValueError 中に包まれている場合 err 価値。 としてエラーにアクセスすることによって ValueError、検証に失敗した実際の値など、タイプが提供する追加のフィールドにアクセスできます。 これは、検証ロジックがプログラムのより深いところで行われ、通常、値にアクセスして、どこで問題が発生したかについてユーザーにヒントを与えることができない場合に役立ちます。 これが役立つ可能性のある別の例は、ネットワークプログラミングを行っていて、net.DNSErrorに遭遇した場合です。 エラーへの参照を取得することにより、エラーが接続できなかったことが原因であるかどうか、またはエラーが接続できたがリソースが見つからなかったことが原因であるかどうかを確認できます。 これを知ったら、さまざまな方法でエラーを処理できます。

見る errors.As 実際には、ファイルを保存し、を使用してプログラムを実行します go run:

  1. go run main.go

出力は次のようになります。

Output
validating 1... value error (1): that's odd validating 2... oh no! validating 3... valid!

今回の出力には、デフォルトは表示されません there was an error: ... すべてのエラーは他のエラーハンドラーによって処理されているため、メッセージ。 検証用の出力 1errors.As エラーチェックが返されました true なぜなら value error ... エラーメッセージが表示されています。 以来 errors.As 関数がtrueを返し、 valueErr 変数は ValueError アクセスして検証に失敗した値を出力するために使用できます valueErr.Value.

のために 2 値、出力はまた、 errUhOh 中に包まれています ValueError ラッパー、 oh no! 特別なエラーハンドラは引き続き実行されます。 これは、を使用する特別なエラーハンドラが原因です。 errors.Is 為に errUhOh のコレクションで最初に来る if エラーを処理するステートメント。 このハンドラーは true の前に errors.As 走っても、特別 oh no! ハンドラーが実行されます。 の場合 errors.As あなたのコードでは、 errors.Isoh no! エラーメッセージは同じになります value error ... として 1 この場合を除いて、値は出力されます value error (2): uh oh.

このセクションでは、プログラムを更新して、 errors.Is に多くの追加の呼び出しを削除する関数 errors.Unwrap エラー処理コードをより堅牢で将来にわたって利用できるようにします。 あなたも使用しました errors.As ラップされたエラーのいずれかが ValueError、次に、値が見つかった場合はその値のフィールドを使用しました。

結論

このチュートリアルでは、を使用してエラーをラップしました %w 動詞をフォーマットし、を使用してエラーをアンラップしました errors.Unwrap. また、をサポートするカスタムエラータイプを作成しました errors.Unwrap あなた自身のコードで。 最後に、カスタムエラータイプを使用して、新しいヘルパー関数を調べました errors.Iserrors.As.

これらの新しいエラー関数を使用すると、作成または操作するエラーに関するより深い情報を簡単に含めることができます。 また、将来的にエラーが深くネストされた場合でもエラーチェックが機能し続けることを保証するために、コードを将来にわたって保証します。

新しいエラー機能の使用方法の詳細については、Goブログに Go1.13でのエラーの処理に関する投稿があります。 エラーパッケージパッケージのドキュメントにも詳細情報が含まれています。

このチュートリアルは、 DigitalOcean How to Code inGoシリーズの一部でもあります。 このシリーズでは、Goの初めてのインストールから、言語自体の使用方法まで、Goに関する多くのトピックを取り上げています。