Goのエラーに追加情報を追加する方法
著者は、 Diversity in Tech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
Goの関数が失敗すると、関数はerror
インターフェイスを使用して値を返し、呼び出し元がその失敗を処理できるようにします。 多くの場合、開発者はfmtパッケージのfmt.Errorf関数を使用してこれらの値を返します。 ただし、Go 1.13より前では、この関数を使用することの欠点は、エラーが返される原因となった可能性のあるエラーに関する情報が失われることです。 これを解決するには、開発者はパッケージを使用してエラーを他のエラー内に「ラップ」する方法を提供するか、struct
エラータイプの1つにError() string
メソッドを実装してカスタムエラーを作成します。 ただし、呼び出し元が明示的に処理する必要のないエラーが多数ある場合は、これらのstruct
タイプを作成するのが面倒な場合があります。そのため、Go 1.13では、言語によって機能が追加され、簡単に処理できるようになりました。これらのケースを処理します。
1つの機能は、fmt.Errorf
関数とerror
値を使用してエラーをラップする機能です。この値は、後でラップ解除して、ラップされたエラーにアクセスできます。 これにより、エラーラッピング機能がGo標準ライブラリに組み込まれるため、サードパーティのライブラリを使用する必要がなくなります。
さらに、関数errors.Isおよびerrors.Asを使用すると、特定のエラーが特定のエラー内のどこかにラップされているかどうかを簡単に判断でき、その特定のエラーにアクセスできます。すべてのエラーを自分でアンラップする必要なしに直接。
このチュートリアルでは、これらの関数を使用して、関数から返されるエラーに追加情報を含めるプログラムを作成してから、ラッピングおよびアンラッピング機能をサポートする独自のカスタムエラーstruct
を作成します。
前提条件
このチュートリアルに従うには、次のものが必要です。
- Go version 1.13 or greater installed. To set this up, follow the How To Install Go tutorial for your operating system.
- (オプション)Go でのエラー処理を読むことは、エラー処理のより詳細な説明のためにこのチュートリアルで役立つかもしれませんが、このチュートリアルは、より高いレベルで同じトピックのいくつかもカバーします。
- (オプション)このチュートリアルは、 Go チュートリアルでのカスタムエラーの作成を拡張し、元のチュートリアル以降にGoに追加された機能を備えています。 前のチュートリアルを読むことは役に立ちますが、厳密には必須ではありません。
Goでのエラーの返却と処理
プログラムでエラーが発生した場合は、ユーザーに表示されないようにそれらのエラーを処理することをお勧めしますが、エラーを処理するには、最初にそれらについて知る必要があります。 Goでは、特別なinterface
タイプであるerror
インターフェースを使用して関数からエラーに関する情報を返すことにより、プログラムのエラーを処理できます。 error
インターフェイスを使用すると、Error() string
メソッドが定義されている限り、すべてのGoタイプをerror
値として返すことができます。 Go標準ライブラリは、 fmt.Errorf 関数など、これらの戻り値に対してerror
を作成する機能を提供します。
このセクションでは、fmt.Errorf
を使用してエラーを返す関数を含むプログラムを作成します。また、関数が返す可能性のあるエラーをチェックするエラーハンドラーを追加します。 (Goでのエラー処理の詳細については、チュートリアル Go でのエラー処理を参照してください。)
多くの開発者は、現在のプロジェクトを保持するためのディレクトリを持っています。 このチュートリアルでは、projects
という名前のディレクトリを使用します。
まず、projects
ディレクトリを作成し、次の場所に移動します。
mkdir projects cd projects
projects
ディレクトリから、新しいerrtutorial
ディレクトリを作成して、新しいプログラムを次の場所に保持します。
mkdir errtutorial
次に、cd
コマンドを使用して新しいディレクトリに移動します。
cd errtutorial
errtutorial
ディレクトリに移動したら、go mod init
コマンドを使用して、errtutorial
という名前の新しいモジュールを作成します。
go mod init errtutorial
Goモジュールを作成したら、nano
またはお気に入りのエディターを使用して、errtutorial
ディレクトリにあるmain.go
という名前のファイルを開きます。
nano main.go
次に、プログラムを作成します。 プログラムは、1
から3
までの数字をループし、validateValue
という関数を使用してそれらの数字が有効かどうかを判断しようとします。 数値が無効であると判断された場合、プログラムはfmt.Errorf
関数を使用して、関数から返されるerror
値を生成します。 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!") } } }
プログラムのvalidateValue
関数は数値を受け取り、有効な値であると判断されたかどうかに基づいてerror
を返します。 このプログラムでは、数値1
が無効であり、エラーthat's odd
を返します。 数値2
は無効であり、エラーuh oh
を返します。 validateValue
関数は、fmt.Errorf
関数を使用して、返されるerror
値を生成します。 fmt.Errorf
関数は、string
を渡す必要なしに、fmt.Printf
またはfmt.Sprintf
と同様のフォーマットを使用してエラーメッセージをフォーマットできるため、エラーを返すのに便利です。 ]からerrors.New
へ。
main
関数では、for
ループは、1
から3
までの各数値を反復処理することから始まり、値を[に格納します。 X151X]変数。 ループ本体の内部で、fmt.Printf
を呼び出すと、プログラムが現在検証している番号が出力されます。 次に、validateValue
関数を呼び出し、現在の番号が検証されているnum
を渡し、エラー結果をerr
変数に格納します。 最後に、err
がnil
でない場合は、検証中にエラーが発生したことを意味し、fmt.Println
を使用してエラーメッセージが出力されます。 エラーチェックのelse
句は、エラーが発生しなかった場合に"valid!"
を出力します。
変更を保存した後、errtutorial
ディレクトリの引数としてmain.go
を指定してgo run
コマンドを使用してプログラムを実行します。
go run main.go
プログラムの実行からの出力は、検証が各番号と番号1
に対して実行され、番号2
が適切なエラーを返したことを示します。
Outputvalidating 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
関数の記述方法では、1
または2
のいずれでもない値に対して、nil
エラー値が返されます。
このセクションでは、fmt.Errorf
を使用して、関数から返すerror
値を作成しました。 また、関数からerror
が返されたときに、エラーメッセージを出力するエラーハンドラーを追加しました。 ただし、エラーが発生したことだけでなく、エラーの意味を知ることが役立つ場合もあります。 次のセクションでは、特定のケースに合わせてエラー処理をカスタマイズする方法を学習します。
Sentinelエラーを使用した特定のエラーの処理
関数からerror
値を受け取った場合、最も基本的なエラー処理は、error
値がnil
であるかどうかを確認することです。 これにより、関数にエラーがあったかどうかがわかりますが、特定のエラーケースに合わせてエラー処理をカスタマイズしたい場合があります。 たとえば、リモートサーバーに接続しているコードがあり、返されるエラー情報は「エラーが発生した」だけであるとします。 エラーの原因がサーバーを利用できなかったのか、接続資格情報が無効だったのかを確認することをお勧めします。 エラーがユーザーの資格情報が間違っていることを意味していることがわかっている場合は、すぐにユーザーに通知することをお勧めします。 ただし、エラーがサーバーを利用できないことを意味する場合は、ユーザーに通知する前に、再接続を数回試行することをお勧めします。 これらのエラーの違いを判断することで、より堅牢でユーザーフレンドリーなプログラムを作成できます。
特定のタイプのエラーをチェックする1つの方法は、error
タイプでError
メソッドを使用してエラーからメッセージを取得し、その値を現在のエラーのタイプと比較することです。探している。 プログラムで、エラー値がuh oh
の場合に、there was an error: 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 }
このコードでは、エラーメッセージにタイプミスが含まれており、uh oh
にo
がありません。 これに気づき、ある時点で修正されたが、いくつかの場所でこのエラーチェックを追加した後でのみ、それらすべての場所でチェックをerr.Error() == "uh oh"
に更新する必要があります。 1文字を変更するだけなので簡単な場合もありますが、uh oh
ではなくuh h
を予期しているため、予期されたカスタムエラーハンドラーは実行されません。
このような場合、特定のエラーを他のエラーとは異なる方法で処理したい場合は、エラー値を保持することを目的とした変数を作成するのが一般的です。 このようにして、コードは文字列ではなくその変数をチェックできます。 通常、これらの変数は、名前が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
は、「uh oh」エラーのエラー値として定義されています(スペルが間違っている場合でも)。 giveMeError
関数は、errUhOh
の値を返します。これは、「uhoh」エラーが発生したことを呼び出し元に通知するためです。 次に、エラー処理コードは、giveMeError
から返されたerr
値をerrUhOh
と比較して、「uhoh」エラーが発生したかどうかを確認します。 タイプミスが見つかって修正された場合でも、エラーチェックはerrUhOh
の値と照合され、errUhOh
の値はエラーの修正バージョンであるため、すべてのコードは引き続き機能します。 giveMeError
が返す値。
この方法でチェックおよび比較することを目的としたエラー値は、センチネルエラーと呼ばれます。 センチネルエラーは、特定の意味で常に比較できる一意の値になるように設計されたエラーです。 上記のerrUhOh
の値は常に同じ意味で、「uh oh」エラーが発生したため、プログラムはエラーをerrUhOh
と比較して、そのエラーが発生したかどうかを判断できます。
Go標準ライブラリは、Goプログラムの開発時に利用できるいくつかのセンチネルエラーも定義しています。 1つの例は、sql.ErrNoRowsエラーです。 sql.ErrNoRows
エラーは、データベースクエリが結果を返さない場合に返されるため、接続エラーとは異なる方法でエラーを処理できます。 これはセンチネルエラーであるため、エラーチェックコードと比較して、クエリが行を返さない場合を知ることができ、プログラムは他のエラーとは異なる方法でそれを処理できます。
通常、センチネルエラー値を作成する場合、これまで使用していたfmt.Errorf
関数の代わりに、errorsパッケージのerrors.New関数が使用されます。 ただし、fmt.Errorf
の代わりにerrors.New
を使用しても、エラーの動作に基本的な変更は加えられず、ほとんどの場合、両方の関数を同じように使用できます。 2つの最大の違いは、errors.New
関数は静的メッセージでのみエラーを作成し、fmt.Errorf
関数は、fmt.Printf
または[ X206X]。 センチネルエラーは値が変化しない基本的なエラーであるため、errors.New
を使用して作成するのが一般的です。
次に、fmt.Errorf
の代わりに「uhoh」エラーにセンチネルエラーを使用するようにプログラムを更新します。
まず、main.go
ファイルを開いて、新しいerrUhOh
センチネルエラーを追加し、それを使用するようにプログラムを更新します。 validateValue
関数が更新され、fmt.Errorf
を使用する代わりにセンチネルエラーが返されるようになりました。 main
関数が更新され、errUhOh
センチネルエラーがチェックされ、検出された場合は、他のエラーについて表示されるthere was an error:
メッセージの代わりにoh no!
が出力されます。
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
を使用してプログラムを再度実行します。
go run main.go
今回の出力には、1
値の一般的なエラー出力が表示されますが、validateValue
から返されたerrUhOh
エラーが表示されると、カスタムoh no!
メッセージが使用されます。 ] 2
の場合:
Outputvalidating 1... there was an error: that's odd validating 2... oh no! validating 3... valid!
エラーチェック内でセンチネルエラーを使用すると、特別なエラーケースの処理が容易になります。 たとえば、 io.EOF センチネルエラーによって示されるファイルの終わりに到達したために、読み取っているファイルが失敗しているかどうか、または一部のファイルが失敗しているかどうかを判断するのに役立ちます。他の理由。
このセクションでは、errors.New
を使用してセンチネルエラーを使用し、特定のタイプのエラーが発生したことを示すGoプログラムを作成しました。 ただし、プログラムが大きくなるにつれて、uh oh
エラー値だけでなくより多くの情報をエラーに含めることができるようになる場合があります。 このエラー値は、エラーが発生した場所や発生した理由に関するコンテキストを提供するものではなく、大規模なプログラムでエラーの詳細を追跡するのは難しい場合があります。 トラブルシューティングを支援し、デバッグの時間を短縮するために、エラーラッピングを利用して必要な詳細を含めることができます。
ラッピングおよびアンラッピングエラー
エラーをラップするということは、ラップされたギフトのように、あるエラー値を取得し、その中に別のエラー値を入れることを意味します。 ただし、包装されたギフトと同様に、中身を知るには包装を解く必要があります。 エラーをラップすると、元のエラー値を失うことなく、エラーの発生場所や発生方法に関する追加情報を含めることができます。これは、エラーがラッパー内にあるためです。
Go 1.13より前では、元のエラーを含むカスタムエラー値を作成できたため、エラーをラップすることができました。 ただし、独自のラッパーを作成するか、すでに作業を行っているライブラリを使用する必要があります。 ただし、Go 1.13では、 errors.Unwrap関数とfmt.Errorf
の%w
動詞を追加することにより、標準ライブラリの一部としてエラーのラップとアンラップのサポートが追加されました。働き。 このセクションでは、%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
を使用して更新されたプログラムを実行します。
go run main.go
出力は次のようになります。
Outputvalidating 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
が含まれるようになりました。 これは、エラーがrunValidation
のfmt.Errorf
によってラップされ、ラップされているエラーの値that's odd
がエラーメッセージに含まれていることを示しています。
次に、しかし、問題があります。 errUhOh
エラーに追加された特別なエラー処理が実行されていません。 2
入力を検証する行を見ると、予想されるoh no!
メッセージではなく、there was an error: run error: uh oh
のデフォルトのエラーメッセージが表示されていることがわかります。 validateValue
関数は、ラップされたエラーの最後に表示されるため、uh oh
エラーを返しますが、errUhOh
のエラー検出は機能しなくなります。 これは、runValidation
によって返されるエラーがerrUhOh
ではなく、fmt.Errorf
によって作成されたラップされたエラーであるために発生します。 if
ステートメントがerr
変数をerrUhOh
と比較しようとすると、err
がerrUhOh
と等しくないため、falseが返されます。さらに、それは wrapping errUhOh
のエラーと同じです。 errUhOh
エラーチェックを修正するには、errors.Unwrap
関数を使用して、ラッパー内からエラーを取得する必要があります。
errors.Unwrap
でエラーをアンラップ
Go1.13で追加された%w
動詞に加えて、いくつかの新しい関数が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!") } } }
編集内容を保存した後、プログラムを再度実行します。
go run main.go
出力は次のようになります。
Outputvalidating 1... there was an error: run error: that's odd validating 2... oh no! validating 3... valid!
これで、出力に、2
入力値のoh no!
エラー処理が戻ったことがわかります。 if
ステートメントに追加した追加のerrors.Unwrap
関数呼び出しにより、err
自体がerrUhOh
値である場合に、errUhOh
を検出できます。また、err
がerrUhOh
を直接ラップしているエラーである場合も同様です。
このセクションでは、fmt.Errorf
に追加された%w
動詞を使用して、errUhOh
エラーを別のエラー内にラップし、追加情報を提供しました。 次に、errors.Unwrap
を使用して、別のエラーにラップされているerrorUhOh
エラーにアクセスしました。 string
値として他のエラー内にエラーを含めることは、エラーメッセージを読む人間にとっては問題ありませんが、プログラムがエラーを処理するのを支援するために、エラーラッパーに追加情報を含める必要がある場合があります。 HTTPリクエストエラー。 これが発生した場合、返す新しいカスタムエラーを作成できます。
カスタムラップエラー
Goのerror
インターフェイスの唯一のルールは、Error
メソッドが含まれていることであるため、多くのGoタイプをカスタムエラーに変えることができます。 1つの方法は、エラーに関する追加情報を使用してstruct
タイプを定義し、Error
メソッドも含めることです。
検証エラーの場合、どの値が実際にエラーを引き起こしたかを知ることが役立つ場合があります。 次に、エラーの原因となったValue
のフィールドと、実際の検証エラーを含むErr
フィールドを含む新しいValueError
構造体を作成しましょう。 カスタムエラータイプは通常、タイプ名の末尾に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
を使用してプログラムを再実行します。
go run main.go
出力は次のようになります。
Outputvalidating 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!
出力では、エラーが出力の前にvalue error:
によってValueError
内にラップされていることがわかります。 ただし、errUhOh
がrunValidation
のValueError
とfmt.Errorf
ラッパーの2層のラッパー内にあるため、uh oh
エラー検出は再び壊れます。 ]。 コードコードはエラー時にerrors.Unwrap
を1回だけ使用するため、最初の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
を使用してプログラムを再度実行します。
go run main.go
出力は次のようになります。
Outputvalidating 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
カスタムエラータイプをアンラップする方法を認識していないために発生します。 カスタムエラーをアンラップするには、内部エラーをerror
値として返す独自のUnwrap
メソッドが必要です。 以前に%w
動詞でfmt.Errorf
を使用してエラーを作成したとき、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
メソッドを保存したら、プログラムを実行します。
go run main.go
出力は次のようになります。
Outputvalidating 1... there was an error: run error: value error: that's odd validating 2... oh no! validating 3... valid!
出力は、errors.Unwrap
がValueError
もアンラップできるようになったため、errUhOh
エラーのoh no!
エラー処理が再び機能していることを示しています。
このセクションでは、新しいカスタムValueError
エラーを作成して、エラーメッセージの一部として検証プロセスに関する情報を自分自身またはユーザーに提供しました。 また、ValueError
にエラーアンラップのサポートを追加して、errors.Unwrap
を使用してラップされたエラーにアクセスできるようにしました。
ただし、エラー処理は少し不格好になり、保守が難しくなっています。 ラッピングの新しいレイヤーがあるたびに、それを処理するためにエラーチェックに別のerrors.Unwrap
を追加する必要がありました。 ありがたいことに、errors
パッケージのerrors.Is
およびerrors.As
関数を使用すると、ラップされたエラーの処理が簡単になります。
ラップされたエラーの処理
プログラムをラップするエラーの潜在的なレイヤーごとに新しいerrors.Unwrap
関数呼び出しを追加する必要がある場合、非常に長くなり、保守が困難になります。 このため、Go1.13リリースのerrors
パッケージにも2つの機能が追加されました。 これらの関数はどちらも、エラーが他のエラーにどれほど深く含まれていても、エラーを操作できるようにすることで、エラーの処理を容易にします。 errors.Is
関数を使用すると、特定のセンチネルエラー値がラップされたエラー内のどこかにあるかどうかを確認できます。 errors.As
関数を使用すると、ラップされたエラー内の任意の場所で特定のタイプのエラーへの参照を取得できます。
errors.Is
でエラー値を確認する
errors.Is
を使用して特定のエラーをチェックすると、errUhOh
の特別なエラー処理が大幅に短縮されます。これは、手動で行っていたネストされたエラーのアンラップをすべて処理するためです。 この関数は2つのerror
パラメーターを取ります。最初のパラメーターは実際に受け取ったエラーであり、2番目のパラメーターはチェックしたいエラーです。
errUhOh
エラー処理をクリーンアップするには、main.go
ファイルを開き、main
関数のerrUhOh
チェックを更新して、代わりに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
を使用してプログラムを再実行します。
go run main.go
出力は次のようになります。
Outputvalidating 1... there was an error: run error: value error: that's odd validating 2... oh no! validating 3... valid!
出力にはoh no!
エラーメッセージが表示されます。これは、errUhOh
のエラーチェックが1つしかない場合でも、エラーチェーンで検出されることを意味します。 errors.Is
は、エラータイプのUnwrap
メソッドを利用して、探しているエラー値、センチネルエラーが見つかるか、[X201X nil
値を返すメソッド。
Goにはエラーラッピングが機能として存在するため、errors.Is
を使用することは、特定のエラーをチェックするための推奨される方法です。 独自のエラー値に使用できるだけでなく、このチュートリアルで前述したsql.ErrNoRows
エラーなどの他のエラー値にも使用できます。
errors.As
を使用したエラータイプの取得
Go1.13でerrors
パッケージに追加された最後の関数は、errors.As
関数です。 この関数は、特定のタイプのエラーへの参照を取得して、それとより詳細に対話する場合に使用されます。 たとえば、前に追加したValueError
カスタムエラーは、エラーのValue
フィールドで検証されている実際の値へのアクセスを提供しますが、そのエラーへの参照がある場合にのみアクセスできます。最初。 これがerrors.As
の出番です。 errors.As
に、errors.Is
と同様のエラーと、エラーのタイプの変数を与えることができます。 次に、エラーチェーンを調べて、ラップされたエラーのいずれかが提供されたタイプと一致するかどうかを確認します。 一致する場合、エラータイプに渡された変数は、エラーerrors.As
が見つかった状態で設定され、関数はtrue
を返します。 一致するエラータイプがない場合は、false
を返します。
errors.As
を使用すると、ValueError
タイプを利用して、エラーハンドラーに追加のエラー情報を表示できるようになりました。 main.go
ファイルをもう一度開き、main
関数を更新して、value error
を出力するValueError
タイプのエラーの新しいエラー処理ケースを追加します。無効な番号、および検証エラー:
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
を使用して、err
値にラップされている場合はValueError
への参照を取得しました。 ValueError
としてエラーにアクセスすることにより、検証に失敗した実際の値など、タイプが提供する追加のフィールドにアクセスできます。 これは、検証ロジックがプログラムのより深いところで行われ、通常、値にアクセスして、どこで問題が発生したかについてユーザーにヒントを与えることができない場合に役立ちます。 これが役立つ可能性のある別の例は、ネットワークプログラミングを行っていて、net.DNSErrorに遭遇した場合です。 エラーへの参照を取得することにより、エラーが接続できなかったことが原因であるかどうか、またはエラーが接続できたがリソースが見つからなかったことが原因であるかどうかを確認できます。 これを知ったら、さまざまな方法でエラーを処理できます。
errors.As
の動作を確認するには、ファイルを保存し、go run
を使用してプログラムを実行します。
go run main.go
出力は次のようになります。
Outputvalidating 1... value error (1): that's odd validating 2... oh no! validating 3... valid!
今回の出力では、すべてのエラーが他のエラーハンドラーによって処理されているため、デフォルトのthere was an error: ...
メッセージは表示されません。 1
を検証するための出力は、value error ...
エラーメッセージが表示されているため、errors.As
エラーチェックがtrue
を返したことを示しています。 errors.As
関数がtrueを返したため、valueErr
変数はValueError
に設定され、valueErr.Value
にアクセスして、検証に失敗した値を出力できます。 ]。
2
値の場合、errUhOh
もValueError
ラッパー内にラップされていても、oh no!
特殊エラーハンドラーが実行されていることも出力に示されます。 。 これは、errUhOh
にerrors.Is
を使用する特別なエラーハンドラが、エラーを処理するif
ステートメントのコレクションの最初に来るためです。 このハンドラーは、errors.As
が実行される前にtrue
を返すため、特別なoh no!
ハンドラーが実行されます。 コード内のerrors.As
がerrors.Is
の前にある場合、oh no!
エラーメッセージは1
値と同じvalue error ...
になります。この場合を除いて、value error (2): uh oh
を出力します。
このセクションでは、errors.Is
関数を使用するようにプログラムを更新して、errors.Unwrap
への多くの追加呼び出しを削除し、エラー処理コードをより堅牢で将来にわたって利用できるようにしました。 また、errors.As
関数を使用して、ラップされたエラーのいずれかがValueError
であるかどうかを確認し、見つかった場合は値のフィールドを使用しました。
結論
このチュートリアルでは、%w
形式の動詞を使用してエラーをラップし、errors.Unwrap
を使用してエラーをアンラップしました。 また、独自のコードでerrors.Unwrap
をサポートするカスタムエラータイプを作成しました。 最後に、カスタムエラータイプを使用して、新しいヘルパー関数errors.Is
およびerrors.As
を調べました。
これらの新しいエラー関数を使用すると、作成または操作するエラーに関するより深い情報を簡単に含めることができます。 また、将来的にエラーが深くネストされた場合でもエラーチェックが機能し続けることを保証するために、コードを将来にわたって保証します。
新しいエラー機能の使用方法の詳細については、Goブログに Go1.13でのエラーの処理に関する投稿があります。 エラーパッケージパッケージのドキュメントにも詳細情報が含まれています。
このチュートリアルは、 DigitalOcean How to Code inGoシリーズの一部でもあります。 このシリーズでは、Goの初めてのインストールから、言語自体の使用方法まで、Goに関する多くのトピックを取り上げています。