前書き

プログラムで発生するエラーは、プログラマが予期したものとそうでないものの2つの大きなカテゴリに分類されます。 https://www.digitalocean.com/community/tutorials/handling-errors-in-go [エラー処理]の前の2つの記事で説明した `+ error `インターフェースは、主に私たちが期待するエラーを処理しますGoプログラムを書いています。 ` error +`インターフェースは、関数呼び出しから発生するエラーのまれな可能性を認識することさえできるので、そのような状況で適切に対応することができます。

パニックはエラーの2番目のカテゴリに分類されますが、これはプログラマが予期しないものです。 これらの予期しないエラーにより、プログラムは自発的に終了し、実行中のGoプログラムを終了します。 よくある間違いは、多くの場合、パニックの原因となります。 このチュートリアルでは、一般的な操作によってGoでパニックが発生する可能性があるいくつかの方法を調べ、それらのパニックを回避する方法についても説明します。 また、https://www.digitalocean.com/community/tutorials/understanding-defer-in-go [+ defer +]ステートメントと `+ recover +`関数を使用して、パニックをキャプチャする機会を得る前に実行中のGoプログラムを予期せず終了します。

パニックを理解する

Goには、自動的にパニックを返し、プログラムを停止する特定の操作があります。 一般的な操作には、https://www.digitalocean.com/community/tutorials/understanding-arrays-and-slices-in-go#arrays [array]の容量を超えたインデックス作成、型アサーションの実行、nilポインターでのメソッドの呼び出し、ミューテックスを使用して、閉じたチャネルで作業しようとします。 これらの状況のほとんどは、プログラムのコンパイル中にコンパイラが検出できない能力があるというプログラミング中のミスに起因します。

パニックには問題の解決に役立つ詳細が含まれているため、開発者は通常、プログラムの開発中に間違いを犯したことを示すためにパニックを使用します。

アウトオブバウンズパニック

スライスの長さまたはアレイの容量を超えてインデックスにアクセスしようとすると、Goランタイムはパニックを生成します。

次の例は、 `+ len +`ビルトインによって返されるスライスの長さを使用して、スライスの最後の要素にアクセスしようとするというよくある間違いを犯します。 このコードを実行して、パニックが発生する理由を確認してください。

package main

import (
   "fmt"
)

func main() {
   names := []string{
       "lobster",
       "sea urchin",
       "sea cucumber",
   }
   fmt.Println("My favorite sea creature is:", names[len(names)])
}

これにより、次の出力が得られます。

Outputpanic: runtime error: index out of range [3] with length 3

goroutine 1 [running]:
main.main()
   /tmp/sandbox879828148/prog.go:13 +0x20

パニックの出力の名前はヒントを提供します: + panic:runtime error:index out of range +。 3つの海の生き物でスライスを作成しました。 次に、 `+ len `組み込み関数を使用して、スライスの長さでスライスにインデックスを付け、スライスの最後の要素を取得しようとしました。 スライスと配列はゼロベースであることを忘れないでください。したがって、最初の要素はゼロで、このスライスの最後の要素はインデックス「+2」にあります。 3番目のインデックス「3」でスライスにアクセスしようとするため、スライスの境界を超えているため、返す要素がスライスにありません。 ランタイムには、不可能なことをするように要求したため、終了して終了する以外のオプションはありません。 Goは、コンパイル中にこのコードがこれを実行しようとすることを証明できないため、コンパイラはこれをキャッチできません。

また、後続のコードが実行されなかったことにも注意してください。 これは、パニックがGoプログラムの実行を完全に停止するイベントだからです。 生成されるメッセージには、パニックの原因の診断に役立つ複数の情報が含まれています。

パニックの解剖学

パニックは、パニックの原因を示すメッセージとhttps://en.wikipedia.org/wiki/Stack_trace [スタックトレース]で構成されており、コード内でパニックが発生した場所を特定するのに役立ちます。

パニックの最初の部分はメッセージです。 それは常に文字列 `+ panic:+`で始まり、パニックの原因によって異なる文字列が続きます。 前の演習のパニックには次のメッセージがあります。

panic: runtime error: index out of range [3] with length 3

`+ panic:`プレフィックスに続く文字列 ` runtime error:`は、言語ランタイムによってパニックが生成されたことを示しています。 このパニックは、スライスの長さ「+3」の範囲外にあるインデックス「+ [3] +」を使用しようとしたことを示しています。

このメッセージに続くのはスタックトレースです。 スタックトレースは、パニックが生成されたときに実行されていたコード行と、そのコードが以前のコードによってどのように呼び出されたかを正確に特定するためのマップを形成します。

goroutine 1 [running]:
main.main()
   /tmp/sandbox879828148/prog.go:13 +0x20

前の例のこのスタックトレースは、プログラムが行番号13のファイル `+ / tmp / sandbox879828148 / prog.go `からパニックを生成したことを示しています。 また、このパニックは ` main `パッケージの ` main()+`関数で生成されたことを示しています。

スタックトレースは、プログラムのhttps://tour.golang.org/concurrency/1[goroutine]ごとに1つのブロックに分かれています。 すべてのGoプログラムの実行は、Goコードの一部をそれぞれ独立して同時に実行できる1つ以上のゴルーチンによって実現されます。 各ブロックはヘッダー `+ goroutine X [state]:+`で始まります。 ヘッダーには、ゴルーチンのID番号と、パニックが発生したときの状態が示されます。 スタックトレースには、ヘッダーの後に、パニックが発生したときにプログラムが実行していた関数と、関数が実行されたファイル名と行番号が表示されます。

前の例のパニックは、スライスへの境界外アクセスによって生成されました。 パニックは、設定されていないポインターでメソッドが呼び出されたときにも生成されます。

無受信機

Goプログラミング言語には、実行時にコンピューターのメモリに存在する特定のタイプのインスタンスを参照するポインターがあります。 ポインターは、値が「+ nil 」であると想定して、何も指していないことを示します。 ` nil +`のポインターでメソッドを呼び出そうとすると、Goランタイムはパニックを生成します。 同様に、インターフェイス型の変数も、メソッドが呼び出されるとパニックを引き起こします。 これらの場合に生成されるパニックを確認するには、次の例を試してください。

package main

import (
   "fmt"
)

type Shark struct {
   Name string
}

func (s *Shark) SayHello() {
   fmt.Println("Hi! My name is", s.Name)
}

func main() {
   s := &Shark{"Sammy"}
   s = nil
   s.SayHello()
}

生成されるパニックは次のようになります。

Outputpanic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xdfeba]

goroutine 1 [running]:
main.(*Shark).SayHello(...)
   /tmp/sandbox160713813/prog.go:12
main.main()
   /tmp/sandbox160713813/prog.go:18 +0x1a

この例では、 + Shark +`という構造体を定義しました。 `+ Shark +`のポインターレシーバーには、 `+ SayHello +`と呼ばれるメソッドが1つ定義されており、呼び出されたときに標準出力に挨拶を出力します。 `+ main`関数の本体内で、この + Shark`構造体の新しいインスタンスを作成し、 `&`演算子を使用してそのポインターを要求します。 このポインターは、 `+ s `変数に割り当てられます。 次に、ステートメント「 s = nil 」を使用して、変数「 s 」を値「 nil 」に再割り当てします。 最後に、変数 ` s `に対して ` SayHello `メソッドを呼び出そうとします。 サミーからフレンドリーメッセージを受信する代わりに、無効なメモリアドレスにアクセスしようとしたというパニックが発生します。これは、 ` s `変数を ` nil +`に設定したためです。

この例では明示的に `+ s `を ` nil `に設定していますが、実際にはこれはそれほど明白ではありません。 ` nil pointer dereference +`に関連するパニックが発生した場合は、作成した可能性のあるポインター変数を適切に割り当てたことを確認してください。

nilポインターと範囲外アクセスから生成されるパニックは、ランタイムによって生成される2つの一般的なパニックです。 組み込み関数を使用して手動でパニックを生成することもできます。

`+ panic`組み込み関数を使用する

`+ panic `組み込み関数を使用して、独自のパニックを生成することもできます。 パニックが生成するメッセージである引数として単一の文字列を取ります。 通常、このメッセージは、エラーを返すようにコードを書き換えるよりも冗長です。 さらに、これを独自のパッケージ内で使用して、パッケージのコードを使用するときに間違いを犯した可能性があることを開発者に示すことができます。 可能な限り、ベストプラクティスは、パッケージのコンシューマに ` error`値を返すことです。

このコードを実行して、別の関数から呼び出された関数から生成されたパニックを確認します。

package main

func main() {
   foo()
}

func foo() {
   panic("oh no!")
}

生成されるパニック出力は次のようになります。

Outputpanic: oh no!

goroutine 1 [running]:
main.foo(...)
   /tmp/sandbox494710869/prog.go:8
main.main()
   /tmp/sandbox494710869/prog.go:4 +0x40

ここでは、文字列 `” oh no! “`で組み込みの `+ panic `を呼び出す関数 ` foo `を定義します。 この関数は、 ` main `関数によって呼び出されます。 出力に「 panic:oh no!」というメッセージが表示され、スタックトレースには、スタックトレースに2行の「 main()」関数と「 foo ()+ `関数。

パニックが発生すると、プログラムが終了するように見えることがわかりました。 これにより、適切に閉じる必要がある開いているリソースがある場合に問題が発生する可能性があります。 Goは、パニックが発生している場合でも、一部のコードを常に実行するメカニズムを提供します。

遅延機能

プログラムには、ランタイムによってパニックが処理されている間でも、適切にクリーンアップする必要があるリソースがあります。 Goでは、呼び出し元の関数の実行が完了するまで、関数呼び出しの実行を延期できます。 遅延機能は、パニックが発生している場合でも実行され、パニックの混againstとした性質から保護するための安全メカニズムとして使用されます。 関数は通常通り呼び出され、 `+ defer sayHello()`のようにステートメント全体に ` defer +`キーワードをプレフィックスとして付けることで延期されます。 この例を実行して、パニックが発生した場合でもメッセージがどのように印刷されるかを確認します。

package main

import "fmt"

func main() {
   defer func() {
       fmt.Println("hello from the deferred function!")
   }()

   panic("oh no!")
}

この例から生成される出力は次のようになります。

Outputhello from the deferred function!
panic: oh no!

goroutine 1 [running]:
main.main()
   /Users/gopherguides/learn/src/github.com/gopherguides/learn//handle-panics/src/main.go:10 +0x55

この例の `+ main `関数内で、最初にメッセージ `” deferred function from hello! “`を出力する匿名関数の呼び出しを ` defer `します。 ` main `関数は、 ` panic `関数を使用してすぐにパニックを引き起こします。 このプログラムの出力では、遅延関数が実行され、そのメッセージが出力されることが最初にわかります。 これに続いて、 ` main +`で生成したパニックが発生します。

遅延機能は、パニックの驚くべき性質に対する保護を提供します。 遅延関数内で、Goは別の組み込み関数を使用してGoプログラムを終了するパニックを停止する機会も提供します。

パニックの処理

パニックには、単一の回復メカニズム、つまり「+ recover +」組み込み関数があります。 この関数を使用すると、コールスタックの途中でパニックをインターセプトし、プログラムが予期せず終了するのを防ぐことができます。 使用には厳格なルールがありますが、本番アプリケーションでは非常に貴重です。

`+ builtin `パッケージの一部であるため、追加のパッケージをインポートせずに ` recover +`を呼び出すことができます。

package main

import (
   "fmt"
   "log"
)

func main() {
   divideByZero()
   fmt.Println("we survived dividing by zero!")

}

func divideByZero() {
   defer func() {
       if err := ; err != nil {
           log.Println("panic occurred:", err)
       }
   }()
   fmt.Println(divide(1, 0))
}

func divide(a, b int) int {
   return a / b
}

この例は出力します:

Output2009/11/10 23:00:00 panic occurred: runtime error: integer divide by zero
we survived dividing by zero!

この例の「+ main 」関数は、定義する関数「 divideByZero 」を呼び出します。 この関数内では、 ` divideByZero `の実行中に発生する可能性のあるパニックを処理する匿名関数の呼び出しを ` defer `します。 この遅延匿名関数内で、 ` recover `組み込み関数を呼び出し、それが返すエラーを変数に割り当てます。 ` divideByZero `がパニック状態の場合、この ` error `値が設定され、そうでない場合は ` nil `になります。 ` err `変数を ` nil `と比較することで、パニックが発生したかどうかを検出できます。この場合、 ` log.Println +`関数を使用してパニックを記録します。 。

この遅延匿名関数に続いて、定義した別の関数 `+ divide `を呼び出し、 ` fmt.Println `を使用して結果を出力しようとします。 指定された引数により、 ` divide +`がゼロによる除算を実行し、パニックが発生します。

この例の出力では、パニックを回復する匿名関数からのログメッセージが最初に表示され、その後に「ゼロで除算して生き残った!」というメッセージが続きます。 これは、Goプログラムを終了させる破滅的なパニックを阻止する組み込み関数 `+ recover +`のおかげで、実際に実行できました。

`+ recover()`から返される ` err `の値は、 ` panic()`の呼び出しに提供された値とまったく同じです。 したがって、パニックが発生していないときにのみ、「 err +」値がnilになるようにすることが重要です。

`+ recover +`でパニックを検出する

`+ recover `関数は、エラーの値に依存して、パニックが発生したかどうかを判断します。 ` panic `関数の引数は空のインターフェースなので、どの型でもかまいません。 空のインターフェイスを含むすべてのインターフェイスタイプのゼロ値は、「 nil 」です。 この例で示すように、「 panic 」の引数として「 nil +」を避けるように注意する必要があります。

package main

import (
   "fmt"
   "log"
)

func main() {
   divideByZero()
   fmt.Println("we survived dividing by zero!")

}

func divideByZero() {
   defer func() {
       if err := recover(); err != nil {
           log.Println("panic occurred:", err)
       }
   }()
   fmt.Println(divide(1, 0))
}

func divide(a, b int) int {
   if b == 0 {
       panic(nil)
   }
   return a / b
}

これは出力されます:

Outputwe survived dividing by zero!

この例は、若干の変更を加えた「+ recover 」を含む前の例と同じです。 ` divide `関数は、除数 ` b `が ` 0 `と等しいかどうかを確認するように変更されました。 もしそうなら、組み込みの ` panic `と引数 ` nil `を使用してパニックを生成します。 今回の出力には、 ` divide `によって作成されたパニックが発生したことを示すログメッセージは含まれていません。 このサイレント動作は、組み込み関数 ` panic `の引数が ` nil +`でないことを保証することが非常に重要な理由です。

結論

Goで `+ panic `を作成する方法と、組み込みの ` recover `を使用してそれらを回復する方法をいくつか見てきました。 必ずしも自分で「+パニック」を使用する必要はありませんが、パニックから適切に回復することは、Goアプリケーションを本番環境に対応させる重要なステップです。

また、https://www.digitalocean.com/community/tutorial_series/how-to-code-in-go [GoのGo Toコード全体のシリーズ]もご覧ください。