Goでのエラーの処理
堅牢なコードは、ユーザー入力の不良、ネットワーク接続の障害、ディスクの障害などの予期しない状況に正しく対応する必要があります。 エラー処理は、プログラムが予期しない状態にあることを識別し、後でデバッグするために診断情報を記録するための手順を実行するプロセスです。
開発者が特殊な構文でエラーを処理する必要がある他の言語とは異なり、Goのエラーは、他の値と同様に関数から返されるタイプerror
の値です。 Goでエラーを処理するには、関数が返す可能性のあるこれらのエラーを調べ、エラーが発生したかどうかを判断し、データを保護するための適切なアクションを実行し、エラーが発生したことをユーザーまたはオペレーターに通知する必要があります。
エラーの作成
エラーを処理する前に、まずエラーを作成する必要があります。 標準ライブラリには、エラーを作成するための2つの組み込み関数errors.New
とfmt.Errorf
が用意されています。 これらの関数はどちらも、後でユーザーに表示できるカスタムエラーメッセージを指定できます。
errors.New
は、単一の引数を取ります。これは、ユーザーに何が悪かったのかを警告するためにカスタマイズできる文字列としてのエラーメッセージです。
次の例を実行して、errors.New
によって作成されたエラーが標準出力に出力されることを確認してください。
package main import ( "errors" "fmt" ) func main() { err := errors.New("barnacles") fmt.Println("Sammy says:", err) }
OutputSammy says: barnacles
標準ライブラリのerrors.New
関数を使用して、文字列"barnacles"
をエラーメッセージとして含む新しいエラーメッセージを作成しました。 ここでは、 Goプログラミング言語スタイルガイドが示唆しているように、エラーメッセージに小文字を使用することで慣例に従いました。
最後に、fmt.Println
関数を使用して、エラーメッセージを"Sammy says:"
と組み合わせました。
fmt.Errorf
関数を使用すると、エラーメッセージを動的に作成できます。 最初の引数は、文字列の場合は%s
、整数の場合は%d
などのプレースホルダー値を持つエラーメッセージを含む文字列です。 fmt.Errorf
は、この書式設定文字列に続く引数を次の順序でこれらのプレースホルダーに補間します。
package main import ( "fmt" "time" ) func main() { err := fmt.Errorf("error occurred at: %v", time.Now()) fmt.Println("An error happened:", err) }
OutputAn error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103
fmt.Errorf
関数を使用して、現在の時刻を含むエラーメッセージを作成しました。 fmt.Errorf
に提供したフォーマット文字列には、fmt.Errorf
にフォーマット文字列の後に提供される最初の引数にデフォルトのフォーマットを使用するように指示する%v
フォーマットディレクティブが含まれています。 その引数は、標準ライブラリのtime.Now
関数によって提供される現在の時刻になります。 前の例と同様に、エラーメッセージを短いプレフィックスと組み合わせ、fmt.Println
関数を使用して結果を標準出力に出力します。
エラーの処理
通常、前の例のように、このように作成されたエラーが他の目的ですぐに使用されることはありません。 実際には、エラーを作成し、問題が発生したときに関数からエラーを返すのがはるかに一般的です。 次に、その関数の呼び出し元は、if
ステートメントを使用して、エラーが存在するかどうか、またはnil
(初期化されていない値)を確認します。
この次の例には、常にエラーを返す関数が含まれています。 プログラムを実行すると、関数が今回エラーを返しているにもかかわらず、前の例と同じ出力が生成されることに注意してください。 別の場所でエラーを宣言しても、エラーのメッセージは変更されません。
package main import ( "errors" "fmt" ) func boom() error { return errors.New("barnacles") } func main() { err := boom() if err != nil { fmt.Println("An error occurred:", err) return } fmt.Println("Anchors away!") }
OutputAn error occurred: barnacles
ここでは、errors.New
を使用して作成した単一のerror
を返すboom()
という関数を定義します。 次に、この関数を呼び出し、err := boom()
の行でエラーをキャプチャします。 このエラーを割り当てたら、if err != nil
条件付きで存在するかどうかを確認します。 ここでは、boom()
から常にerror
を返すため、条件は常にtrue
に評価されます。
これは常に当てはまるとは限らないため、エラーが存在しない場合(nil
)とエラーが存在する場合をロジック処理することをお勧めします。 エラーが存在する場合は、fmt.Println
を使用して、前の例で行ったように、プレフィックスとともにエラーを出力します。 最後に、return
ステートメントを使用して、fmt.Println("Anchors away!")
の実行をスキップします。これは、エラーが発生しなかった場合にのみ実行されるためです。
注:最後の例に示されているif err != nil
構造は、Goプログラミング言語でのエラー処理の主力製品です。 関数がエラーを生成する可能性がある場合は常に、if
ステートメントを使用してエラーが発生したかどうかを確認することが重要です。 このように、慣用的なGoコードには、当然、最初のインデントレベルに「ハッピーパス」ロジックがあり、2番目のインデントレベルにすべての「サッドパス」ロジックがあります。
Ifステートメントには、関数の呼び出しとそのエラーの処理を要約するために使用できるオプションの割り当て句があります。
次のプログラムを実行して、前の例と同じ出力を確認しますが、今回は複合if
ステートメントを使用して、ボイラープレートを減らします。
package main import ( "errors" "fmt" ) func boom() error { return errors.New("barnacles") } func main() { if err := boom(); err != nil { fmt.Println("An error occurred:", err) return } fmt.Println("Anchors away!") }
OutputAn error occurred: barnacles
前と同じように、常にエラーを返す関数boom()
があります。 boom()
から返されたエラーをif
ステートメントの最初の部分としてerr
に割り当てます。 if
ステートメントの2番目の部分では、セミコロンに続いて、そのerr
変数が使用可能になります。 エラーが存在するかどうかを確認し、以前と同じように短いプレフィックス文字列を使用してエラーを出力します。
このセクションでは、エラーのみを返す関数の処理方法を学習しました。 これらの関数は一般的ですが、複数の値を返す可能性のある関数からのエラーを処理できることも重要です。
値と一緒にエラーを返す
単一のエラー値を返す関数は、多くの場合、データベースへの行の挿入など、ステートフルな変更に影響を与える関数です。 正常に完了した場合に値を返し、その関数が失敗した場合に潜在的なエラーを返す関数を作成することも一般的です。 Goを使用すると、関数が複数の結果を返すことができます。これを使用して、値とエラータイプを同時に返すことができます。
複数の値を返す関数を作成するために、関数のシグニチャの括弧内に各戻り値のタイプをリストします。 たとえば、string
とerror
を返すcapitalize
関数は、func capitalize(name string) (string, error) {}
を使用して宣言されます。 (string, error)
の部分は、この関数がstring
とerror
をこの順序で返すことをGoコンパイラに通知します。
次のプログラムを実行して、string
とerror
の両方を返す関数からの出力を確認します。
package main import ( "errors" "fmt" "strings" ) func capitalize(name string) (string, error) { if name == "" { return "", errors.New("no name provided") } return strings.ToTitle(name), nil } func main() { name, err := capitalize("sammy") if err != nil { fmt.Println("Could not capitalize:", err) return } fmt.Println("Capitalized name:", name) }
OutputCapitalized name: SAMMY
capitalize()
を、文字列(大文字にする名前)を受け取り、文字列とエラー値を返す関数として定義します。 main()
では、capitalize()
を呼び出し、関数から返された2つの値をname
変数とerr
変数に、左側のコンマで区切って割り当てます。 :=
オペレーターの手側。 この後、前の例と同様にif err != nil
チェックを実行し、エラーが存在する場合はfmt.Println
を使用してエラーを標準出力に出力します。 エラーがない場合は、Capitalized name: SAMMY
を出力します。
name, err := capitalize("sammy")
の文字列"sammy"
を空の文字列("")
に変更してみてください。代わりに、エラーCould not capitalize: no name provided
が表示されます。
capitalize
関数は、関数の呼び出し元がname
パラメーターに空の文字列を指定すると、エラーを返します。 name
パラメーターが空の文字列でない場合、capitalize()
はstrings.ToTitle
を使用してname
パラメーターを大文字にし、エラー値としてnil
を返します。 。
この例に従ういくつかの微妙な規則があります。これはGoコードに典型的ですが、Goコンパイラーによって強制されません。 関数がエラーを含む複数の値を返す場合、規則では、最後の項目としてerror
を返すように要求されます。 複数の戻り値を持つ関数からerror
を返す場合、慣用的なGoコードは、エラー以外の各値をゼロ値に設定します。 ゼロ値は、たとえば、文字列の場合は空の文字列、整数の場合は0
、構造体タイプの場合は空の構造体、インターフェイスおよびポインタタイプの場合はnil
です。 ゼロ値については、変数と定数に関するチュートリアルで詳しく説明しています。
ボイラープレートを減らす
関数から返す値が多い状況では、これらの規則に従うのは面倒になる可能性があります。 匿名関数を使用して、ボイラープレートを減らすことができます。 匿名関数は、変数に割り当てられたプロシージャです。 前の例で定義した関数とは対照的に、それらは宣言した関数内でのみ使用できます。これにより、再利用可能なヘルパーロジックの短い部分として機能するのに最適です。
次のプログラムは、最後の例を変更して、大文字にする名前の長さを含めます。 返す値が3つあるため、エラーの処理は、私たちを支援する匿名関数がないと面倒になる可能性があります。
package main import ( "errors" "fmt" "strings" ) func capitalize(name string) (string, int, error) { handle := func(err error) (string, int, error) { return "", 0, err } if name == "" { return handle(errors.New("no name provided")) } return strings.ToTitle(name), len(name), nil } func main() { name, size, err := capitalize("sammy") if err != nil { fmt.Println("An error occurred:", err) } fmt.Printf("Capitalized name: %s, length: %d", name, size) }
OutputCapitalized name: SAMMY, length: 5
main()
内で、capitalize
から返された3つの引数を、それぞれname
、size
、およびerr
としてキャプチャします。 次に、err
変数がnil
と等しくないかどうかを確認することにより、capitalize
がerror
を返したかどうかを確認します。 これは、capitalize
によって返される他の値を使用する前に行うことが重要です。これは、無名関数handle
がそれらをゼロ値に設定する可能性があるためです。 文字列"sammy"
を指定したためエラーが発生しなかったため、大文字の名前とその長さを出力します。
もう一度、"sammy"
を空の文字列("")
に変更して、エラーケースが出力されることを確認できます(An error occurred: no name provided
)。
capitalize
内で、handle
変数を無名関数として定義します。 単一のエラーを受け取り、capitalize
の戻り値と同じ順序で同じ値を返します。 handle
は、これらの値をゼロ値に設定し、引数として渡されたerror
を最終的な戻り値として転送します。 これを使用して、error
をパラメーターとしてhandle
の呼び出しの前にreturn
ステートメントを使用することにより、capitalize
で発生したエラーを返すことができます。 。
capitalize
は、常に3つの値を返す必要があることに注意してください。これは、関数を定義した方法だからです。 関数が返す可能性のあるすべての値を処理したくない場合があります。 幸い、割り当て側でこれらの値を使用する方法にはある程度の柔軟性があります。
マルチリターン関数からのエラーの処理
関数が多くの値を返す場合、Goではそれぞれを変数に割り当てる必要があります。 最後の例では、capitalize
関数から返される2つの値の名前を指定してこれを行います。 これらの名前はコンマで区切って、:=
演算子の左側に表示する必要があります。 capitalize
から返された最初の値は、name
変数に割り当てられ、2番目の値(error
)は変数err
に割り当てられます。 場合によっては、エラー値のみに関心があります。 特別な_
変数名を使用して、関数が返す不要な値を破棄できます。
次のプログラムでは、capitalize
関数を含む最初の例を変更して、空の文字列("")
を渡すことでエラーを生成しました。 このプログラムを実行して、_
変数で最初の戻り値を破棄することにより、エラーだけを調べる方法を確認してください。
package main import ( "errors" "fmt" "strings" ) func capitalize(name string) (string, error) { if name == "" { return "", errors.New("no name provided") } return strings.ToTitle(name), nil } func main() { _, err := capitalize("") if err != nil { fmt.Println("Could not capitalize:", err) return } fmt.Println("Success!") }
OutputCould not capitalize: no name provided
今回のmain()
関数では、大文字の名前(string
が最初に返されます)をアンダースコア変数(_
)に割り当てます。 同時に、capitalize
によって返されるerror
をerr
変数に割り当てます。 次に、if err != nil
条件にエラーが存在するかどうかを確認します。 行_, err := capitalize("")
のcapitalize
への引数として空の文字列をハードコーディングしているため、この条件は常にtrue
と評価されます。 これにより、if
ステートメントの本体内のfmt.Println
関数の呼び出しによって出力される出力"Could not capitalize: no name provided"
が生成されます。 この後のreturn
は、fmt.Println("Success!")
をスキップします。
結論
標準ライブラリを使用してエラーを作成する多くの方法と、慣用的な方法でエラーを返す関数を作成する方法を見てきました。 このチュートリアルでは、標準ライブラリerrors.New
およびfmt.Errorf
関数を使用してさまざまなエラーを正常に作成することができました。 今後のチュートリアルでは、独自のカスタムエラータイプを作成して、より豊富な情報をユーザーに伝える方法を見ていきます。