著者は、 Diversity in Tech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

Goの関数が失敗すると、関数はerrorインターフェイスを使用して値を返し、呼び出し元がその失敗を処理できるようにします。 多くの場合、開発者はfmtパッケージのfmt.Errorf関数を使用してこれらの値を返します。 ただし、Go 1.13より前では、この関数を使用することの欠点は、エラーが返される原因となった可能性のあるエラーに関する情報が失われることです。 これを解決するには、開発者はパッケージを使用してエラーを他のエラー内に「ラップ」する方法を提供するか、structエラータイプの1つにError() stringメソッドを実装してカスタムエラーを作成します。 ただし、呼び出し元が明示的に処理する必要のないエラーが多数ある場合は、これらのstructタイプを作成するのが面倒な場合があります。そのため、Go 1.13では、言語によって機能が追加され、簡単に処理できるようになりました。これらのケースを処理します。

1つの機能は、fmt.Errorf関数とerror値を使用してエラーをラップする機能です。この値は、後でラップ解除して、ラップされたエラーにアクセスできます。 これにより、エラーラッピング機能がGo標準ライブラリに組み込まれるため、サードパーティのライブラリを使用する必要がなくなります。

さらに、関数errors.Isおよびerrors.Asを使用すると、特定のエラーが特定のエラー内のどこかにラップされているかどうかを簡単に判断でき、その特定のエラーにアクセスできます。すべてのエラーを自分でアンラップする必要なしに直接。

このチュートリアルでは、これらの関数を使用して、関数から返されるエラーに追加情報を含めるプログラムを作成してから、ラッピングおよびアンラッピング機能をサポートする独自のカスタムエラーstructを作成します。

前提条件

このチュートリアルに従うには、次のものが必要です。

  • Goバージョン1.13以降がインストールされています。これは、シリーズGoのローカルプログラミング環境をインストールおよびセットアップする方法に従って実行できます。
  • (オプション)Go でのエラー処理を読むことは、エラー処理のより詳細な説明のためにこのチュートリアルで役立つかもしれませんが、このチュートリアルは、より高いレベルで同じトピックのいくつかもカバーします。
  • (オプション)このチュートリアルは、 Go チュートリアルでのカスタムエラーの作成を拡張し、元のチュートリアル以降にGoに追加された機能を備えています。 前のチュートリアルを読むことは役に立ちますが、厳密には必須ではありません。

Goでのエラーの返却と処理

プログラムでエラーが発生した場合は、ユーザーに表示されないようにそれらのエラーを処理することをお勧めしますが、エラーを処理するには、最初にそれらについて知る必要があります。 Goでは、特別なinterfaceタイプであるerrorインターフェイスを使用して関数からエラーに関する情報を返すことにより、プログラムのエラーを処理できます。 errorインターフェイスを使用すると、Error() stringメソッドが定義されている限り、すべてのGoタイプをerror値として返すことができます。 Go標準ライブラリは、 fmt.Errorf 関数など、これらの戻り値に対してerrorを作成する機能を提供します。

このセクションでは、fmt.Errorfを使用してエラーを返す関数を含むプログラムを作成します。また、関数が返す可能性のあるエラーをチェックするエラーハンドラーを追加します。 (Goでのエラー処理の詳細については、チュートリアル Go でのエラー処理を参照してください。)

多くの開発者は、現在のプロジェクトを保持するためのディレクトリを持っています。 このチュートリアルでは、projectsという名前のディレクトリを使用します。

まず、projectsディレクトリを作成し、次の場所に移動します。

  1. mkdir projects
  2. cd projects

projectsディレクトリから、新しいerrtutorialディレクトリを作成して、新しいプログラムを次の場所に保持します。

  1. mkdir errtutorial

次に、cdコマンドを使用して新しいディレクトリに移動します。

  1. cd errtutorial

errtutorialディレクトリに移動したら、go mod initコマンドを使用して、errtutorialという名前の新しいモジュールを作成します。

  1. go mod init errtutorial

Goモジュールを作成したら、nanoまたはお気に入りのエディターを使用して、errtutorialディレクトリにあるmain.goという名前のファイルを開きます。

  1. nano main.go

次に、プログラムを作成します。 プログラムは、1から3までの数字をループし、validateValueという関数を使用してそれらの数字が有効かどうかを判断しようとします。 数値が無効であると判断された場合、プログラムはfmt.Errorf関数を使用して、関数から返されるerror値を生成します。 fmt.Errorf関数を使用すると、error値を作成できます。ここで、エラーメッセージは関数に提供するメッセージです。 fmt.Printfと同様に機能しますが、メッセージを画面に出力する代わりに、errorとして返します。

次に、main関数で、エラー値がnil値であるかどうかを確認します。 nilの値の場合、関数は成功し、valid!メッセージが出力されます。 そうでない場合は、受信したエラーが代わりに出力されます。

プログラムを開始するには、次のコードをmain.goファイルに追加します。

projects / errtutorial / main.go
package main

import (
	"fmt"
)

func validateValue(number int) error {
	if number == 1 {
		return fmt.Errorf("that's odd")
	} else if number == 2 {
		return fmt.Errorf("uh oh")
	}
	return nil
}

func main() {
	for num := 1; num <= 3; num++ {
		fmt.Printf("validating %d... ", num)
		err := validateValue(num)
		if err != nil {
			fmt.Println("there was an error:", err)
		} else {
			fmt.Println("valid!")
		}
	}
}

プログラムのvalidateValue関数は数値を受け取り、有効な値であると判断されたかどうかに基づいてerrorを返します。 このプログラムでは、数値1が無効であり、エラーthat's oddを返します。 数値2は無効であり、エラーuh ohを返します。 validateValue関数は、fmt.Errorf関数を使用して、返されるerror値を生成します。 fmt.Errorf関数は、stringを渡す必要なしに、fmt.Printfまたはfmt.Sprintfと同様のフォーマットを使用してエラーメッセージをフォーマットできるため、エラーを返すのに便利です。 ]からerrors.Newへ。

main関数では、forループは、1から3までの各数値を反復処理することから始まり、値を[に格納します。 X151X]変数。 ループ本体の内部で、fmt.Printfを呼び出すと、プログラムが現在検証している番号が出力されます。 次に、validateValue関数を呼び出し、現在の番号が検証されているnumを渡し、エラー結果をerr変数に格納します。 最後に、errnilでない場合は、検証中にエラーが発生したことを意味し、fmt.Printlnを使用してエラーメッセージが出力されます。 エラーチェックのelse句は、エラーが発生しなかった場合に"valid!"を出力します。

変更を保存した後、errtutorialディレクトリの引数としてmain.goを指定してgo runコマンドを使用してプログラムを実行します。

  1. go run main.go

プログラムの実行からの出力は、検証が各番号および番号1に対して実行され、番号2が適切なエラーを返したことを示します。

Output
validating 1... there was an error: that's odd validating 2... there was an error: uh oh validating 3... valid!

プログラムからの出力を見ると、プログラムが3つの数値すべてを検証しようとしたことがわかります。 validateValue関数がthat's oddエラーを返したと最初に言ったとき、これは1の値に予想されます。 次の値2もエラーを返したことを示していますが、今回はuh ohエラーでした。 最後に、3値は、エラー値としてnilを返します。これは、エラーがなく、数値が有効であることを意味します。 validateValue関数の記述方法では、1または2のいずれでもない値に対して、nilエラー値が返されます。

このセクションでは、fmt.Errorfを使用して、関数から返すerror値を作成しました。 また、関数からerrorが返されたときに、エラーメッセージを出力するエラーハンドラーを追加しました。 ただし、エラーが発生したことだけでなく、エラーの意味を知ることが役立つ場合もあります。 次のセクションでは、特定のケースに合わせてエラー処理をカスタマイズする方法を学習します。

Sentinelエラーを使用した特定のエラーの処理

関数からerror値を受け取った場合、最も基本的なエラー処理は、error値がnilであるかどうかを確認することです。 これにより、関数にエラーがあったかどうかがわかりますが、特定のエラーケースに合わせてエラー処理をカスタマイズしたい場合があります。 たとえば、リモートサーバーに接続しているコードがあり、返されるエラー情報は「エラーが発生した」だけであるとします。 エラーの原因がサーバーを利用できなかったのか、接続資格情報が無効だったのかを確認することをお勧めします。 エラーがユーザーの資格情報が間違っていることを意味していることがわかっている場合は、すぐにユーザーに通知することをお勧めします。 ただし、エラーがサーバーを利用できないことを意味する場合は、ユーザーに通知する前に、再接続を数回試行することをお勧めします。 これらのエラーの違いを判断することで、より堅牢でユーザーフレンドリーなプログラムを作成できます。

特定のタイプのエラーをチェックする1つの方法は、errorタイプでErrorメソッドを使用してエラーからメッセージを取得し、その値を現在のエラーのタイプと比較することです。探している。 プログラムで、エラー値がuh ohの場合に、there was an error: uh oh以外のメッセージを表示したいとします。 このケースを処理するための1つのアプローチは、次のようにErrorメソッドから返された値をチェックすることです。

if err.Error() == "uh oh" {
	// Handle 'uh oh' error.
	fmt.Println("oh no!")
}

この場合、上記のコードのように、err.Error()の文字列値をチェックして、それが値uh ohであるかどうかを確認します。 ただし、uh ohエラーstringがプログラムの他の場所でわずかに異なる場合、コードは機能しません。 この方法でエラーをチェックすると、エラーがチェックされるすべての場所を更新する必要があるため、エラーのメッセージ自体を更新する必要がある場合にも、コードが大幅に更新される可能性があります。 たとえば、次のコードを考えてみましょう。

func giveMeError() error {
	return fmt.Errorf("uh h")
}

err := giveMeError()
if err.Error() == "uh h" {
	// "uh h" error code
}

このコードでは、エラーメッセージにタイプミスが含まれており、uh ohoがありません。 これに気づき、ある時点で修正されたが、いくつかの場所でこのエラーチェックを追加した後でのみ、それらすべての場所でチェックをerr.Error() == "uh oh"に更新する必要があります。 1文字を変更するだけなので簡単な場合もありますが、uh ohではなくuh hを予期しているため、予期されたカスタムエラーハンドラーは実行されません。

このような場合、特定のエラーを他のエラーとは異なる方法で処理したい場合は、エラー値を保持することを目的とした変数を作成するのが一般的です。 このようにして、コードは文字列ではなくその変数をチェックできます。 通常、これらの変数は、名前がerrまたはErrで始まり、エラーであることを示します。 エラーが定義されているパッケージ内でのみ使用されることを意図している場合は、errプレフィックスを使用することをお勧めします。 エラーが他の場所で使用されることを意図している場合は、代わりにErrプレフィックスを使用して、関数またはstructと同様に、エクスポートされた値にします。

ここで、前のタイプミスの例でこれらのエラー値の1つを使用していたとしましょう。

var errUhOh = fmt.Errorf("uh h")

func giveMeError() error {
	return errUhOh
}

err := giveMeError()
if err == errUhOh {
	// "uh oh" error code
}

この例では、変数errUhOhは、「uh oh」エラーのエラー値として定義されています(スペルが間違っている場合でも)。 giveMeError関数は、errUhOhの値を返します。これは、「uhoh」エラーが発生したことを呼び出し元に通知するためです。 次に、エラー処理コードは、giveMeErrorから返されたerr値をerrUhOhと比較して、「uhoh」エラーが発生したかどうかを確認します。 タイプミスが見つかって修正された場合でも、エラーチェックはerrUhOhの値と照合され、errUhOhの値はエラーの修正バージョンであるため、すべてのコードは引き続き機能します。 giveMeErrorが返す値。

この方法でチェックおよび比較することを目的としたエラー値は、センチネルエラーと呼ばれます。 センチネルエラーは、特定の意味で常に比較できる一意の値になるように設計されたエラーです。 上記のerrUhOhの値は常に同じ意味で、「uh oh」エラーが発生したため、プログラムはエラーをerrUhOhと比較して、そのエラーが発生したかどうかを判断できます。

Go標準ライブラリは、Goプログラムの開発時に利用できるいくつかのセンチネルエラーも定義しています。 1つの例は、sql.ErrNoRowsエラーです。 sql.ErrNoRowsエラーは、データベースクエリが結果を返さない場合に返されるため、接続エラーとは異なる方法でエラーを処理できます。 これはセンチネルエラーであるため、エラーチェックコードと比較して、クエリが行を返さない場合を知ることができ、プログラムは他のエラーとは異なる方法でそれを処理できます。

通常、センチネルエラー値を作成する場合、これまで使用していたfmt.Errorf関数の代わりに、errorsパッケージのerrors.New関数が使用されます。 ただし、fmt.Errorfの代わりにerrors.Newを使用しても、エラーの動作に基本的な変更は加えられず、ほとんどの場合、両方の関数を同じように使用できます。 2つの最大の違いは、errors.New関数は静的メッセージでのみエラーを作成し、fmt.Errorf関数は、fmt.Printfまたは[ X206X]