Goでカスタムエラーを作成する
序章
Goは、標準ライブラリにエラーを作成するための2つのメソッド、errors.Newとfmt.Errorfを提供します。 より複雑なエラー情報をユーザーに、またはデバッグ時に将来の自分に伝える場合、これら2つのメカニズムでは、何が起こったかを適切にキャプチャして報告するには不十分な場合があります。 このより複雑なエラー情報を伝達し、より多くの機能を実現するために、標準ライブラリインターフェイスタイプエラーを実装できます。
この構文は次のようになります。
type error interface {
Error() string
}
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
ここでは、新しい空の構造体タイプを作成しました。 MyError
、および定義 Error()
その上でメソッド。 The Error()
メソッドは文字列を返します "boom"
.
内部 main()
、関数を呼び出します sayHello
空の文字列との新しいインスタンスを返します MyError
. 以来 sayHello
常にエラーを返します、 fmt.Println
のifステートメントの本文内での呼び出し main()
常に実行されます。 次に、 fmt.Println
短いプレフィックス文字列を出力するには "unexpected error:"
のインスタンスと一緒に MyError
内で開催 err
変数。
直接電話する必要がないことに注意してください Error()
、以来 fmt
パッケージは、これがの実装であることを自動的に検出できます error
. 呼び出す Error()
透過的に文字列を取得します "boom"
プレフィックス文字列と連結します "unexpected error: 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
前の例のように。
以内 Error()
の方法 RequestError
、使用します fmt.Sprintf
エラーが作成されたときに提供された情報を使用して文字列を作成する関数。
タイプアサーションとカスタムエラー
The error
インターフェイスは1つのメソッドのみを公開しますが、他のメソッドにアクセスする必要がある場合があります error
エラーを適切に処理するための実装。 たとえば、次のカスタム実装がいくつかある場合があります。 error
これは一時的なものであり、再試行できます。 Temporary()
方法。
インターフェイスは、タイプによって提供されるメソッドの幅広いセットへの狭いビューを提供するため、タイプアサーションを使用して、ビューが表示するメソッドを変更するか、ビューを完全に削除する必要があります。
次の例は、 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()
、私たちは doRequest()
これは error
私たちへのインターフェース。 まず、によって返されたエラーメッセージを出力します Error()
方法。 次に、からのすべてのメソッドを公開しようとします RequestError
タイプアサーションを使用する re, ok := err.(*RequestError)
. タイプアサーションが成功した場合は、 Temporary()
このエラーが一時的なエラーであるかどうかを確認するメソッド。 以来 StatusCode
によって設定されました doRequest()
は 503
、一致する http.StatusServiceUnavailable
、これは true
と原因 "This request can be tried again"
印刷する。 実際には、メッセージを出力するのではなく、代わりに別のリクエストを行います。
ラッピングエラー
通常、エラーは、データベース、ネットワーク接続など、プログラムの外部から生成されます。 これらのエラーから提供されるエラーメッセージは、エラーの原因を見つけるのに役立ちません。 エラーメッセージの先頭に追加情報でエラーをラップすると、デバッグを成功させるために必要なコンテキストが提供されます。
次の例は、コンテキスト情報を他の方法では不可解なものに添付する方法を示しています 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
、 と error
これ WrappedError
についての詳細情報を提供しています。 いつ Error()
メソッドが呼び出され、再び使用します fmt.Sprintf
コンテキストメッセージを出力するには、 error
(fmt.Sprintf
暗黙的に呼び出すことを知っています Error()
メソッドも)。
内部 main()
、を使用してエラーを作成します errors.New
、次に、を使用してそのエラーをラップします Wrap
定義した関数。 これにより、これを示すことができます error
で生成されました "main"
. また、 WrappedError
も error
、他をラップすることができます WrappedError
s-これにより、エラーの原因を突き止めるのに役立つチェーンを確認できます。 標準ライブラリの助けを少し借りれば、完全なスタックトレースをエラーに埋め込むこともできます。
結論
以来 error
インターフェイスは単一の方法にすぎません。さまざまな状況でさまざまなタイプのエラーを提供する際に、非常に柔軟性があることがわかりました。 これには、エラーの一部として複数の情報を伝達することから、指数バックオフを実装することまで、すべてを網羅できます。 Goのエラー処理メカニズムは一見単純に見えるかもしれませんが、これらのカスタムエラーを使用して、一般的な状況と一般的でない状況の両方を処理することで、非常に豊富な処理を実現できます。
Goには、予期しない動作であるパニックを伝える別のメカニズムがあります。 エラー処理シリーズの次の記事では、パニックとは何か、パニックの処理方法について説明します。