堅牢なコードは、ユーザー入力の不良、ネットワーク接続の障害、ディスクの障害などの予期しない状況に正しく対応する必要があります。 エラー処理は、プログラムが予期しない状態にあることを識別し、後でデバッグするために診断情報を記録するための手順を実行するプロセスです。

開発者が特殊な構文でエラーを処理する必要がある他の言語とは異なり、Goのエラーは次のタイプの値です。 error 他の値と同様に関数から返されます。 Goでエラーを処理するには、関数が返す可能性のあるこれらのエラーを調べ、エラーが発生したかどうかを判断し、データを保護するための適切なアクションを実行し、エラーが発生したことをユーザーまたはオペレーターに通知する必要があります。

エラーの作成

エラーを処理する前に、まずエラーを作成する必要があります。 標準ライブラリには、エラーを作成するための2つの組み込み関数が用意されています。 errors.Newfmt.Errorf. これらの関数はどちらも、後でユーザーに表示できるカスタムエラーメッセージを指定できます。

errors.New 単一の引数を取ります。エラーメッセージを文字列として受け取り、カスタマイズしてユーザーに問題を警告することができます。

次の例を実行して、によって作成されたエラーを確認してください。 errors.New 標準出力に出力:

package main

import (
	"errors"
	"fmt"
)

func main() {
	err := errors.New("barnacles")
	fmt.Println("Sammy says:", err)
}
Output
Sammy says: barnacles

使用しました errors.New 標準ライブラリの関数を使用して、文字列を含む新しいエラーメッセージを作成します "barnacles" エラーメッセージとして。 ここでは、 Goプログラミング言語スタイルガイドが示唆しているように、エラーメッセージに小文字を使用することで慣例に従いました。

最後に、 fmt.Println エラーメッセージをと組み合わせる関数 "Sammy says:".

The 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)
}
Output
An error happened: Error occurred at: 2019-07-11 16:52:42.532621 -0400 EDT m=+0.000137103

使用しました fmt.Errorf 現在の時刻を含むエラーメッセージを作成する関数。 提供したフォーマット文字列 fmt.Errorf が含まれています %v 指示するフォーマットディレクティブ fmt.Errorf フォーマット文字列の後に提供される最初の引数にデフォルトのフォーマットを使用します。 その引数は、によって提供される現在の時刻になります 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!")
}
Output
An error occurred: barnacles

ここでは、という関数を定義します boom() シングルを返します error を使用して構築する errors.New. 次に、この関数を呼び出して、次の行でエラーをキャプチャします err := boom(). このエラーを割り当てたら、エラーが存在するかどうかを確認します。 if err != nil 条件付き。 ここで、条件は常に次のように評価されます true、私たちは常に error から boom().

これは常に当てはまるとは限らないため、エラーが存在しない場合にロジックを処理することをお勧めします(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!")
}
Output
An error occurred: barnacles

前と同じように、関数があります。 boom()、常にエラーを返します。 から返されたエラーを割り当てます boom()err の最初の部分として if 声明。 の第2部では if セミコロンに続くステートメント、その err その後、変数が使用可能になります。 エラーが存在するかどうかを確認し、以前と同じように短いプレフィックス文字列を使用してエラーを出力します。

このセクションでは、エラーのみを返す関数の処理方法を学習しました。 これらの関数は一般的ですが、複数の値を返す可能性のある関数からのエラーを処理できることも重要です。

値と一緒にエラーを返す

単一のエラー値を返す関数は、多くの場合、データベースへの行の挿入など、ステートフルな変更に影響を与える関数です。 正常に完了した場合に値を返し、その関数が失敗した場合に潜在的なエラーを返す関数を作成することも一般的です。 Goを使用すると、関数が複数の結果を返すことができます。これを使用して、値とエラータイプを同時に返すことができます。

複数の値を返す関数を作成するために、関数のシグニチャの括弧内に各戻り値のタイプをリストします。 たとえば、 capitalize を返す関数 stringerror を使用して宣言されます func capitalize(name string) (string, error) {}. The (string, error) 一部は、この関数が stringerror、この順序で。

次のプログラムを実行して、両方を返す関数からの出力を確認します。 stringerror:

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)
}
Output
Capitalized name: SAMMY

定義する capitalize() 文字列(大文字にする名前)を受け取り、文字列とエラー値を返す関数として。 の main()、私たちは capitalize() 関数から返された2つの値をに割り当てます nameerr 変数の左側をコンマで区切って := オペレーター。 この後、私たちは if err != nil 前の例のようにチェックし、を使用してエラーを標準出力に出力します fmt.Println エラーが存在した場合。 エラーがなかった場合は、印刷します Capitalized name: SAMMY.

文字列を変更してみてください "sammy"name, err := capitalize("sammy") 空の文字列に ("") エラーが発生します Could not capitalize: no name provided 代わりは。

The 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)
}
Output
Capitalized name: SAMMY, length: 5

内部 main()、ここで、から返された3つの引数をキャプチャします。 capitalize なので name, size、 と err、 それぞれ。 次に、次のことを確認します capitalize を返しました error かどうかを確認することによって err 変数が等しくなかった nil. これは、によって返される他の値を使用する前に行うことが重要です。 capitalize、無名関数なので、 handle、それらをゼロ値に設定できます。 文字列を提供したため、エラーは発生しなかったため "sammy"、大文字の名前とその長さを出力します。

もう一度、変更してみてください "sammy" 空の文字列に ("") 印刷されたエラーケースを確認するには(An error occurred: no name provided).

内部 capitalize、定義します handle 匿名関数としての変数。 単一のエラーを受け取り、の戻り値と同じ順序で同じ値を返します。 capitalize. handle それらの値をゼロ値に設定し、 error 最終的な戻り値として引数として渡されます。 これを使用して、で発生したエラーを返すことができます capitalize を使用して return 呼び出しの前のステートメント handle とともに error そのパラメータとして。

それを覚えておいてください capitalize 関数を定義した方法であるため、常に3つの値を返す必要があります。 関数が返す可能性のあるすべての値を処理したくない場合があります。 幸い、割り当て側でこれらの値を使用する方法にはある程度の柔軟性があります。

マルチリターン関数からのエラーの処理

関数が多くの値を返す場合、Goではそれぞれを変数に割り当てる必要があります。 最後の例では、から返された2つの値の名前を指定してこれを行います。 capitalize 関数。 これらの名前はコンマで区切って、左側に表示する必要があります := オペレーター。 から返される最初の値 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!")
}
Output
Could not capitalize: no name provided

以内 main() 今回の関数では、大文字の名前を割り当てます( string 最初に返される)アンダースコア変数(_). 同時に、 error によって返される capitalizeerr 変数。 次に、エラーがに存在するかどうかを確認します if err != nil 条件付き。 空の文字列を引数としてハードコーディングしたので capitalize 行で _, err := capitalize("")、この条件は常に次のように評価されます true. これにより、出力が生成されます "Could not capitalize: no name provided" への呼び出しによって印刷されます fmt.Println 体内で機能する if 声明。 The return この後スキップします fmt.Println("Success!").

結論

標準ライブラリを使用してエラーを作成する多くの方法と、慣用的な方法でエラーを返す関数を作成する方法を見てきました。 このチュートリアルでは、標準ライブラリを使用してさまざまなエラーを正常に作成することができました errors.Newfmt.Errorf 機能。 今後のチュートリアルでは、独自のカスタムエラータイプを作成して、より豊富な情報をユーザーに伝える方法を見ていきます。