Goのエラーに追加情報を追加する方法

提供:Dev Guides
移動先:案内検索

著者は、 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変数に格納します。 最後に、errnilでない場合は、検証中にエラーが発生したことを意味し、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 ohoがありません。 これに気づき、ある時点で修正されたが、いくつかの場所でこのエラーチェックを追加した後でのみ、それらすべての場所でチェックを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が含まれるようになりました。 これは、エラーがrunValidationfmt.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と比較しようとすると、errerrUhOhと等しくないため、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を検出できます。また、errerrUhOhを直接ラップしているエラーである場合も同様です。

このセクションでは、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内にラップされていることがわかります。 ただし、errUhOhrunValidationValueErrorfmt.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.UnwrapValueErrorもアンラップできるようになったため、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値の場合、errUhOhValueErrorラッパー内にラップされていても、oh no!特殊エラーハンドラーが実行されていることも出力に示されます。 。 これは、errUhOherrors.Isを使用する特別なエラーハンドラが、エラーを処理するifステートメントのコレクションの最初に来るためです。 このハンドラーは、errors.Asが実行される前にtrueを返すため、特別なoh no!ハンドラーが実行されます。 コード内のerrors.Aserrors.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に関する多くのトピックを取り上げています。