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

開発者が特殊な構文でエラーを処理する必要がある他の言語とは異なり、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 Programming Language Style Guideに示されているように、エラーメッセージに小文字を使用することにより、ここでの規則に従っています。

最後に、 `+ 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ステートメントにオプションの代入句があり、関数の呼び出しとそのエラーの処理を簡略化するために使用できます。

次のプログラムを実行して、前の例と同じ出力を確認しますが、今回は定型文を減らすために複合 `+ 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()」があります。 ` if `ステートメントの最初の部分として、 ` boom()`から返されたエラーを ` 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大文字:代わりに名前を指定しない+ `。

関数の呼び出し元が `+ name `パラメータに空の文字列を提供すると、 ` capitalize `関数はエラーを返します。 ` name `パラメータが空の文字列ではない場合、 ` capitalize()`は ` strings.ToTitle `を使用して ` name `パラメータを大文字にし、エラー値に対して ` nil +`を返します。

この例に続く、Goコードに特有の、Goコンパイラによって強制されない、いくつかの微妙な規則があります。 関数がエラーを含む複数の値を返す場合、慣例により、最後の項目として「+ error 」を返すように要求されます。 複数の戻り値を持つ関数から ` error `を返す場合、慣用的なGoコードは各非エラー値を_zero value_に設定します。 ゼロ値は、たとえば、文字列の空の文字列、整数の ` 0 `、構造体の型の空の構造体、およびインターフェイス型とポインタ型の ` nil +`です。 ゼロ値については、https://www.digitalocean.com/community/tutorials/how-to-use-variables-and-constants-in-go#zero-values [変数と定数に関するチュートリアル]で詳しく説明しています。

ボイラープレートの削減

これらの規則を順守することは、関数から返される値が多数ある状況では退屈になる可能性があります。 ボイラープレートを減らすためにhttps://en.wikipedia.org/wiki/Anonymous_function[anonymous function]を使用できます。 無名関数は、変数に割り当てられたプロシージャです。 前の例で定義した関数とは対照的に、それらは宣言した関数内でのみ使用できます。これにより、再利用可能なヘルパーロジックの短い断片として機能するのに最適です。

次のプログラムは、大文字の名前の長さを含めるように最後の例を修正します。 返される値は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 "+`を空の文字列 `+(" ")+`に変更して、エラーケースを印刷してみてください( `+エラーが発生しました:名前が指定されていません)。

+ 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 +`が最初に返されます)を割り当てます。 同時に、 `+ capitalized`によって返される + error`を `+ error`変数に割り当てます。 次に、エラーが `+ if err!= nil `条件に存在するかどうかを確認します。 空の文字列を ` _、err:= capitalize(” “)`の行の ` capitalize `の引数としてハードコーディングしているため、この条件は常に ` true `に評価されます。 これにより、 ` if `ステートメントの本文内で ` fmt.Println `関数の呼び出しによって出力される出力 `”大文字にすることができません:名前が指定されていません “`が生成されます。 この後の ` return `は、 ` fmt.Println(” Success! “)+`をスキップします。

結論

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