前書き

Goには、標準ライブラリでエラーを作成するための2つの方法、https://www.digitalocean.com/community/tutorials/handling-errors-in-go#creating-errors [+ errors.New +`および `+ fmt.Errorf + `]。 より複雑なエラー情報をユーザーに、またはデバッグ時に将来の自分に伝える場合、これらの2つのメカニズムでは、何が起こったのかを適切にキャプチャして報告するには不十分な場合があります。 このより複雑なエラー情報を伝え、より多くの機能を実現するために、標準ライブラリインターフェイスタイプhttps://golang.org/pkg/builtin/#error [+ error +`]を実装できます。

この構文は次のとおりです。

type error interface {
 Error() string
}

https://golang.org/pkg/builtin/ [+ builtin +]パッケージは、エラーメッセージを文字列として返す単一の `+ Error()`メソッドを持つインターフェースとして ` error +`を定義します。 このメソッドを実装することにより、定義した型を独自のエラーに変換できます。

次の例を実行して、 `+ error +`インターフェースの実装を見てみましょう。

package main

import (
   "fmt"
   "os"
)

type MyError struct{}

func (m *MyError) Error() string {
   return "boom"
}

func sayHello() (string, error) {
   return "", &MyError{}
}

func main() {
   s, err := sayHello()
   if err != nil {
       fmt.Println("unexpected error: err:", err)
       os.Exit(1)
   }
   fmt.Println("The string:", s)
}

次の出力が表示されます。

Outputunexpected error: err: boom
exit status 1

ここで、新しい空のstruct型である `+ MyError `を作成し、 ` Error()`メソッドを定義しました。 ` Error()`メソッドは文字列 `” boom “+`を返します。

`+ main()`内で、空の文字列と ` MyError `の新しいインスタンスを返す関数 ` sayHello `を呼び出します。 ` sayHello `は常にエラーを返すため、 ` main()`のifステートメントの本体内での ` fmt.Println `呼び出しは常に実行されます。 次に、 ` fmt.Println `を使用して、短いプレフィックス文字列 `” unexpected error: “`と、 ` err `変数内に保持されている ` MyError +`のインスタンスを出力します。

+ fmt +`パッケージはこれが `+ error +`の実装であることを自動的に検出できるため、 `+ Error()+`を直接呼び出す必要はありません。 文字列 `+" boom "+`を取得するために `+ Error()+ transparentlyを呼び出し、プレフィックス文字列 "予期しないエラー:err: "

カスタムエラーで詳細情報を収集する

カスタムエラーは、詳細なエラー情報を取得する最もクリーンな方法である場合があります。 たとえば、HTTPリクエストによって生成されたエラーのステータスコードをキャプチャするとします。次のプログラムを実行して、その情報をきれいにキャプチャできる `+ error +`の実装を確認します。

package main

import (
   "errors"
   "fmt"
   "os"
)

type RequestError struct {
   StatusCode int

   Err error
}

func (r *RequestError) Error() string {
   return fmt.Sprintf("status %d: err %v", r.StatusCode, r.Err)
}

func doRequest() error {
   return &RequestError{
       StatusCode: 503,
       Err:        errors.New("unavailable"),
   }
}

func main() {
   err := doRequest()
   if err != nil {
       fmt.Println(err)
       os.Exit(1)
   }
   fmt.Println("success!")
}

次の出力が表示されます。

Outputstatus 503: err unavailable
exit status 1

この例では、 `+ RequestError `の新しいインスタンスを作成し、標準ライブラリの ` errors.New `関数を使用してステータスコードとエラーを提供します。 次に、前の例のように、 ` fmt.Println +`を使用してこれを印刷します。

`+ RequestError `の ` Error()`メソッド内で、 ` fmt.Sprintf +`関数を使用して、エラーの作成時に提供された情報を使用して文字列を作成します。

型アサーションとカスタムエラー

+ error`インターフェースは1つのメソッドのみを公開しましたが、エラーを適切に処理するために + error`実装の他のメソッドにアクセスする必要があるかもしれません。 たとえば、一時的な + error`のカスタム実装がいくつかあり、 + Temporary()+ `メソッドの存在によって再試行することができます。

インターフェイスは、型によって提供されるメソッドの幅広いセットに狭いビューを提供します。そのため、ビューが表示するメソッドを変更するか、ビューを完全に削除するには、_type assertion_を使用する必要があります。

次の例では、前述の `+ RequestError `を拡張して、呼び出し元が要求を再試行するかどうかを示す ` Temporary()+`メソッドを追加します。

package main

import (
   "errors"
   "fmt"
   "net/http"
   "os"
)

type RequestError struct {
   StatusCode int

   Err error
}

func (r *RequestError) Error() string {
   return r.Err.Error()
}

func (r *RequestError) Temporary() bool {
   return r.StatusCode == http.StatusServiceUnavailable // 503
}

func doRequest() error {
   return &RequestError{
       StatusCode: 503,
       Err:        errors.New("unavailable"),
   }
}

func main() {
   err := doRequest()
   if err != nil {
       fmt.Println(err)
       re, ok := err.(*RequestError)
       if ok {
           if re.Temporary() {
               fmt.Println("This request can be tried again")
           } else {
               fmt.Println("This request cannot be tried again")
           }
       }
       os.Exit(1)
   }

   fmt.Println("success!")
}

次の出力が表示されます。

Outputunavailable
This request can be tried again
exit status 1

+ main()+`内で、 `+ error`インターフェイスを返す + Request()+ `を呼び出します。 最初に、 `+ Error()`メソッドによって返されたエラーメッセージを出力します。 次に、型アサーション ` re、ok:= err。(* RequestError)`を使用して、 ` RequestError `からすべてのメソッドを公開しようとします。 型のアサーションが成功した場合、 ` Temporary()`メソッドを使用して、このエラーが一時的なエラーであるかどうかを確認します。 ` doRequest()`によって設定された ` StatusCode `は ` http.StatusServiceUnavailable `に一致する ` 503 `であるため、これは ` true `を返し、 `”この要求を再試行できます “+ `印刷されます。 実際には、メッセージを出力するのではなく、代わりに別の要求を作成します。

ラッピングエラー

一般的に、データベース、ネットワーク接続など、プログラムの外部からエラーが生成されます。 これらのエラーから提供されるエラーメッセージは、エラーの原因を特定するのに役立ちません。 エラーメッセージの先頭にエラーを追加情報でラップすると、デバッグを成功させるために必要なコンテキストが提供されます。

次の例は、他の関数から返された、不可解な `+ error +`にコンテキスト情報を添付する方法を示しています。

package main

import (
   "errors"
   "fmt"
)

type WrappedError struct {
   Context string
   Err     error
}

func (w *WrappedError) Error() string {
   return fmt.Sprintf("%s: %v", w.Context, w.Err)
}

func Wrap(err error, info string) *WrappedError {
   return &WrappedError{
       Context: info,
       Err:     err,
   }
}

func main() {
   err := errors.New("boom!")
   err = Wrap(err, "main")

   fmt.Println(err)
}

次の出力が表示されます。

Outputmain: boom!

+ WrappedError +`は2つのフィールドを持つ構造体です: `+ string +`としてのコンテキストメッセージと、この `+ WrappedError +`が詳細な情報を提供する `+ error ++ Error()+`メソッドが呼び出されると、再度 `+ fmt.Sprintf +`を使用してコンテキストメッセージを出力し、 `+ error +( `+ fmt.Sprintf `は暗黙的に ` Error( )+ `メソッドも)。

+ main()+`内で、 `+ errors.New +`を使用してエラーを作成し、定義した `+ Wrap +`関数を使用してそのエラーをラップします。 これにより、この `+ error`が + “main” + `で生成されたことを示すことができます。 また、「+ WrappedError 」は「 error」でもあるため、他の「+ WrappedError」をラップできます。これにより、エラーの原因を突き止めるのに役立つチェーンを確認できます。 標準ライブラリの少しの助けを借りて、エラーに完全なスタックトレースを埋め込むこともできます。

結論

`+ error`インターフェースは単一のメソッドにすぎないため、さまざまな状況に応じてさまざまなタイプのエラーを提供できる柔軟性が非常に高いことがわかりました。 これには、エラーの一部として複数の情報を伝達することから、https://en.wikipedia.org/wiki/Exponential_backoff [exponential backoff]を実装することまで、すべてを含めることができます。 Goのエラー処理メカニズムは一見単純に見えるかもしれませんが、これらのカスタムエラーを使用して非常に豊富な処理を実現し、一般的な状況とまれな状況の両方を処理できます。

Goには、パニックという予期しない動作を伝える別のメカニズムがあります。 エラー処理シリーズの次の記事では、パニックについて調べます。パニックとは何か、その処理方法を説明します。