errorインターフェースGoでは、エラー処理を例外で行うのではありません。
代わりに、エラーは組み込みのerrorインターフェースを実装した型の、普通の_値_です。
errorインターフェースはごく最小限のものです。
エラーメッセージを文字列として返すError()メソッドを1つだけ持ちます。
type error interface {
Error() string
}
関数を定義するとき、その実行中にエラーが発生し、それが呼び出し元に届く必要があるなら、戻り値の型の1つにerrorを含める必要があります。
関数が複数の戻り値を持つ場合、慣習としてerrorは常に最後にします。
func DoSomething() (int, error) {
// ...
}
エラーインターフェースを常に自分で実装しなければならないわけではありません。
シンプルなエラーを作るには、標準ライブラリのパッケージerrorsに含まれるerrors.New()関数を使えます。
渡す必要があるのはエラーメッセージの文字列だけで、errors.New()がそのメッセージを含みerrorインターフェースを実装した値を作ってくれます。
関数がエラーを返す場合は、他のすべての戻り値にはゼロ値を返すのがよい習慣です。
func DoSomething() (SomeStruct, int, error) {
// ...
return SomeStruct{}, 0, errors.New("failed to calculate result")
}
エラーがあるときに、すべての関数が他の戻り値にゼロ値を返すと想定してはいけません。 エラーが返された場合は、他の戻り値はどれも安全に使えると思わないのがよい習慣です。
例えば、ある関数がファイルから読み込もうとして、途中まで埋まったバイトスライスとエラーを返したとします。 エラーがあったからこのバイトスライスは常に空だと思い込んで、返されたバイトスライスをコードで再利用すると、気づきにくいバグを招くことがあります。
唯一の例外は、関数のドキュメントに、エラーの場合でも他の戻り値が意味を持つとはっきり書かれている場合です。
例については、[バッファのWriteのドキュメント][buffer-write]を見てみましょう。
エラーにはデフォルトでスタックトレースが含まれないので、エラーメッセージはできるだけ具体的にしてください。 慣習として、エラーメッセージは小文字で始め、ピリオドで終わらないようにします。
そのようなエラーを複数の場所で使いたい場合(パッケージの利用者にそのエラーを公開したい場合も同様です)は、errors.Newをその場で書くのではなく、エラー用の変数を宣言します。
慣習として、変数の名前はErrまたはerrで始めます(エクスポートされるかどうかで使い分けます)。
このようなエラー変数は、_センチネルエラー_と呼ばれることがよくあります。
import "errors"
var ErrNotFound = errors.New("resource was not found")
func DoSomething() error {
// ...
return ErrNotFound
}
関数の実行中にエラーがなかったことを示すには、エラーとしてnilを返します。
func Foo() (int, error) {
return 10, nil
}
いくつかの変数からより複雑なエラーメッセージを組み立てたいときは、文字列フォーマットを利用できます。
Goには、このための特別なfmt.Errorf関数があります。
エラーメッセージの文字列を組み立てて、エラーを返します。
input := 123
action := "UPDATE"
err := fmt.Errorf("invalid input %d for action %s", input, action)
err.Error()
// => "invalid input 123 for action UPDATE"
エラーを返す関数を呼び出すときは、エラーの値をerrという変数に保存するのが一般的です。
関数の実際の結果を使う前に、エラーがなかったかどうかを確認する必要があります。
==と!=を使ってエラーをnilと比較でき、errがnilでなければエラーがあったとわかります。
コードの「ハッピーパス」が入れ子になるのを避けるために、エラーのケースを先に処理します。多くの場合は早期リターンを使います。 詳しくは、Mat Ryerの有名な記事を参照してください。
// Do this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
return err
}
// do something with file
}
// Not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err != nil {
// handle err
} else {
// do something with file
}
}
// Also not this:
func myFunc() error {
file, err := os.Open("./users.csv")
if err == nil {
// do something with file
}
// handle err
}
ほとんどの場合、上の最初の例のように、エラーは関数のスタックをさかのぼって返されます。 エラーの別の処理方法として、ログに記録してから別の処理を続けることもできます。 エラーは返すかログに記録するかのどちらかにするのがよく、両方はしません。
Goのほとんどの関数は戻り値の1つとしてエラーを含むので、Goのコードのあちこちでif err != nilパターンを見たり使ったりすることになるでしょう。
エラーにエラーメッセージの文字列以上の情報を含めたい場合は、独自のエラーの型を作れます。
前述のとおり、errorインターフェースを実装するもの(つまりError() stringメソッドを持つもの)は、Goではエラーとして扱えます。
通常、独自のエラーの型を作るには構造体を使います。
慣習として、独自のエラーの型の名前はErrorで終わらせます。
また、Error() stringメソッドはポインタレシーバで定義するのが最善です。その理由はこちらのStackoverflowのコメントで学べます。
つまり、独自のエラーへのポインタを返す必要があることに注意してください。そうしないと、非ポインタの値はError() stringメソッドを提供しないため、errorとして扱われません。
type MyCustomError struct {
message string
details string
}
func (e *MyCustomError) Error() string {
return fmt.Sprintf("%s, details: %s", e.message, e.details)
}
func someFunction() error {
// ...
return &MyCustomError{
message: "...",
details: "...",
}
}