『GoLang』エラー処理
GoはJavaやNETのような
Goの設計者は
Goは普通のエラーをどのように処理しますか?関数およびメソッドでエラー・オブジェクトを一意または最後の戻り値として返すことによって、
エラーを処理し、関数にエラーが発生した場所でユーザーにエラー情報を返します.このように処理すれば、本当に問題が発生しても、あなたのプログラムは実行し続け、ユーザーに通知することができます.
ライブラリ関数は、通常、プライマリ・コール関数にエラー・プロンプトを返す必要があります.
Goエラー条件のチェックと報告の慣習:エラーが発生した関数は、2つの変数、1つの値、および1つのエラーコードを返します.後者が エラー発生時に実行中の関数(必要であればプログラム全体)が中止されるのを防止するために、関数呼び出し後にエラーをチェックする必要があります.
コードをより明確にするには、エラー値変数を含む
1.エラー処理
Goにはあらかじめ定義された
エラー値は異常状態を表すために使用され、errorsパケットには
1.1定義エラー
新しいエラー・タイプが必要な場合は、次のように
例:
平方根関数を計算するパラメータテストに使用できます.
次のようにSqrt関数を呼び出すことができます.
ほとんどの場合、カスタムエラー構造タイプは、(低レベルの)エラー情報以外の有用な情報、例えば、進行中の操作(ファイルを開くなど)、フルパスまたは名前を含むことができる.以下の例の
異なるエラー条件が発生する可能性がある場合、実際のエラーに対してタイプ断言またはタイプ判定(type-switch)を使用することは有用であり、エラーシーンに基づいていくつかの救済およびリカバリ操作を行うことができる.
または、
1.2 fmtでエラーオブジェクトを作成する
通常、エラーパラメータを含むより情報量の多い文字列を返したい場合があります.たとえば、
たとえば、前の平方根の例では、次のように使用します.
2つ目の例:コマンドラインから入力を読み込むときにhelpフラグを付けると、有用な情報でエラーを生成できます.
2.運転時異常とpanic
配列の下限やタイプ断言に失敗したような実行エラーが発生すると、Go実行時に実行時panicがトリガーされ、プログラムのクラッシュに伴って
出力は次のとおりです.
既知のユーザーによってプログラムが起動されたかどうかを確認する具体的な例:
これらは、パッケージをインポートした
エラーが発生してプログラムを中止する必要がある場合、
Go panicking:
多層ネストされた関数呼び出しで
勝手に
3.panicから復旧(Recover)
名前の通り、この
次の例の
これはpanic,defer,recoverがどのように結合して使用されるかを示す完全な例です.
出力:
直観的に見ると、
4.カスタムパッケージのエラー処理とpanicking
これは、すべてのカスタムパッケージ実装者が遵守すべきベストプラクティスです.
1)パッケージ内において、常に
パケット内部、特に非導出関数に深いネストされた呼び出しがある場合、
次のコードはこの点をよく述べています.
出力:
try/catch
異常メカニズム:投げ異常操作は実行できません.しかし、defer-panic-and-recover
メカニズムがあります.Goの設計者は
try/catch
メカニズムの使用が氾濫しすぎて、底からより高いレベルに異常を投げて資源を消費していると思っています.彼らがGoに設計したメカニズムは異常を「捕捉」することもできるが,より軽量であり,(誤りを処理する)最後の手段とすべきである.Goは普通のエラーをどのように処理しますか?関数およびメソッドでエラー・オブジェクトを一意または最後の戻り値として返すことによって、
nil
を返すとエラーは発生せず、プライマリ・コール(calling)関数は常に受信したエラーをチェックする必要があります.エラーを処理し、関数にエラーが発生した場所でユーザーにエラー情報を返します.このように処理すれば、本当に問題が発生しても、あなたのプログラムは実行し続け、ユーザーに通知することができます.
panic and recover
は、通常のエラーではなく、真の例外(予測不可能なエラー)を処理するために使用されます.ライブラリ関数は、通常、プライマリ・コール関数にエラー・プロンプトを返す必要があります.
Goエラー条件のチェックと報告の慣習:
nil
であれば成功、非 nil
であればエラーが発生します.if value, err := pack1.Func1(param1); err != nil {
fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), param1)
return // or: return err
} else {
// Process(value)
}
コードをより明確にするには、エラー値変数を含む
if
複合文を常に使用する必要があります.1.エラー処理
Goにはあらかじめ定義された
error
インタフェースタイプがあるtype error interface {
Error() string
}
エラー値は異常状態を表すために使用され、errorsパケットには
errorString
構造体が error
インタフェースを実現している.プログラムがエラー状態にある場合はos.Exit(1)
で実行を中止できます.1.1定義エラー
新しいエラー・タイプが必要な場合は、次のように
errors
パッケージのerrors.New
関数で適切なエラー情報を受信して作成できます.err := errors.New("math - square root of negative number")
例:
// errors.go
package main
import (
"errors"
"fmt"
)
var errNotFound error = errors.New("Not found error")
func main() {
fmt.Printf("error: %v", errNotFound)
}
// error: Not found error
平方根関数を計算するパラメータテストに使用できます.
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New ("math - square root of negative number")
}
// implementation of Sqrt
}
次のようにSqrt関数を呼び出すことができます.
if f, err := Sqrt(-1); err != nil {
fmt.Printf("Error: %s
", err)
}
fmt.Printf
メソッドが自動的に呼び出されるため、エラーメッセージ「Error:math-square root of negative number」が印刷されます.通常(エラーメッセージ)には「Error:」のような接頭辞があるので、エラーメッセージは大文字で始まるのはやめましょう.ほとんどの場合、カスタムエラー構造タイプは、(低レベルの)エラー情報以外の有用な情報、例えば、進行中の操作(ファイルを開くなど)、フルパスまたは名前を含むことができる.以下の例の
String()
操作がタッチしたos.Open
エラーを参照:// PathError records an error and the operation and file path that caused it.
type PathError struct {
Op string // "open", "unlink", etc.
Path string // The associated file.
Err error // Returned by the system call.
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": "+ e.Err.Error()
}
異なるエラー条件が発生する可能性がある場合、実際のエラーに対してタイプ断言またはタイプ判定(type-switch)を使用することは有用であり、エラーシーンに基づいていくつかの救済およびリカバリ操作を行うことができる.
// err != nil
if e, ok := err.(*os.PathError); ok {
// remedy situation
}
または、
switch err := err.(type) {
case ParseError:
PrintParseError(err)
case PathError:
PrintPathError(err)
...
default:
fmt.Printf("Not a special error, just %s
", err)
}
1.2 fmtでエラーオブジェクトを作成する
通常、エラーパラメータを含むより情報量の多い文字列を返したい場合があります.たとえば、
PathError
で実現できます.これはfmt.Errorf()
と全く同じで、1つ以上のフォーマットプレースホルダのフォーマット文字列と対応する数のプレースホルダ変数を受信します.印刷情報とは異なり、エラーオブジェクトを情報で生成します.たとえば、前の平方根の例では、次のように使用します.
if f < 0 {
return 0, fmt.Errorf("math: square root of negative number %g", f)
}
2つ目の例:コマンドラインから入力を読み込むときにhelpフラグを付けると、有用な情報でエラーを生成できます.
if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
err = fmt.Errorf("usage: %s infile.txt outfile.txt", filepath.Base(os.Args[0]))
return
}
2.運転時異常とpanic
配列の下限やタイプ断言に失敗したような実行エラーが発生すると、Go実行時に実行時panicがトリガーされ、プログラムのクラッシュに伴って
fmt.Printf()
インタフェースタイプの値が投げ出されます.このエラー値には、通常のエラーを区別するためのruntime.Error
メソッドがあります.RuntimeError()
コードから直接初期化できます:エラー条件(我々がテストしたコード)非常に過酷で回復不可能であり、プログラムが実行を継続できない場合、panic
関数を使用してプログラムの実行を中止するエラーを生成することができる.panic
は任意のタイプのパラメータを受信し、通常は文字列であり、プログラムが死亡したときに印刷される.Go実行時にプログラムを中止し、デバッグ情報を与える.package main
import "fmt"
func main() {
fmt.Println("Starting the program")
panic("A severe error occurred: stopping the program!")
fmt.Println("Ending the program")
}
出力は次のとおりです.
Starting the program
panic: A severe error occurred: stopping the program!
panic PC=0x4f3038
runtime.panic+0x99 /go/src/pkg/runtime/proc.c:1032
runtime.panic(0x442938, 0x4f08e8)
main.main+0xa5 E:/Go/GoBoek/code examples/chapter 13/panic.go:8
main.main()
runtime.mainstart+0xf 386/asm.s:84
runtime.mainstart()
runtime.goexit /go/src/pkg/runtime/proc.c:148
runtime.goexit()
---- Error run E:/Go/GoBoek/code examples/chapter 13/panic.exe with code Crashed
---- Program exited with code -1073741783
既知のユーザーによってプログラムが起動されたかどうかを確認する具体的な例:
var user = os.Getenv("USER")
func check() {
if user == "" {
panic("Unknown user: no value for $USER")
}
}
これらは、パッケージをインポートした
panic
関数で確認できます.エラーが発生してプログラムを中止する必要がある場合、
init()
はエラー処理モードに使用できます.if err != nil {
panic("ERROR occurred:" + err.Error())
}
Go panicking:
多層ネストされた関数呼び出しで
panic
を呼び出すと、現在の関数の実行をすぐに中止することができ、すべてのpanic
文は実行を保証し、defer
を受信した関数呼び出し者に制御権を返す.このように最上位層まで泡を上げて、panic
を実行し、スタックの上部でプログラムがクラッシュし、コマンドラインでdefer
に渡された値でエラーを報告します.この終了プロセスはpanic
です.勝手に
panicking
でプログラムを中止することはできません.エラーを修正してプログラムを実行し続けるようにしなければなりません.3.panicから復旧(Recover)
名前の通り、この
panic
組み込み関数はrecover
またはエラーシーンから復元するために使用されます.プログラムはpanickingから制御権を再取得し、終了プロセスを停止し、正常な実行に戻ります.panic
はrecover
修飾の関数でのみ使用できます.defer
呼び出しで渡されたエラー値を取得するには、正常に実行されている場合、呼び出しpanic
はnilを返し、他の効果はありません.recover
は、panic
修飾defer
が呼び出されるか、プログラムが中止されるまでスタックが展開される.次の例の
recover()
関数呼び出し関数パラメータprotect
は、g
から放出されるランタイムg
を保護するために使用者を保護し、panic
の情報を示す.func protect(g func()) {
defer func() {
log.Println("done")
// Println executes normally even if there is a panic
if err := recover(); err != nil {
log.Printf("run time panic: %v", err)
}
}()
log.Println("start")
g() // possible runtime-error
}
これはpanic,defer,recoverがどのように結合して使用されるかを示す完全な例です.
// panic_recover.go
package main
import (
"fmt"
)
func badCall() {
panic("bad end")
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicing %s\r
", e)
}
}()
badCall()
fmt.Printf("After bad call\r
") //
出力:
Calling test
Panicing bad end
Test completed
直観的に見ると、
panic
を投げ出した後、panic
関数内に直接入り、defer
があれば実行を継続し、直接投げ出しミスはないrecover()
は、ある意味でdefer-panic-recover
,if
のような制御フロー機構でもある.4.カスタムパッケージのエラー処理とpanicking
これは、すべてのカスタムパッケージ実装者が遵守すべきベストプラクティスです.
1)パッケージ内において、常に
for
の中からpanic
:明示的なパッケージ範囲外のrecover
2)パケットの呼び出し元にエラー値を返す(panic()
)パケット内部、特に非導出関数に深いネストされた呼び出しがある場合、
panic
をpanic
に変換して、呼び出し元がなぜエラーを起こしたのかを伝えるのが実用的である(コードの可読性が向上する).次のコードはこの点をよく述べています.
// parse.go
package parse
import (
"fmt"
"strings"
"strconv"
)
// A ParseError indicates an error in converting a word into an integer.
type ParseError struct {
Index int // The index into the space-separated list of words.
Word string // The word that generated the parse error.
Err error // The raw error that precipitated this error, if any.
}
// String returns a human-readable error message.
func (e *ParseError) String() string {
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}
// Parse parses the space-separated words in in put as integers.
func Parse(input string) (numbers []int, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
fields := strings.Fields(input)
numbers = fields2numbers(fields)
return
}
func fields2numbers(fields []string) (numbers []int) {
if len(fields) == 0 {
panic("no words to parse")
}
for idx, field := range fields {
num, err := strconv.Atoi(field)
if err != nil {
panic(&ParseError{idx, field, err})
}
numbers = append(numbers, num)
}
return
}
// panic_package.go
package main
import (
"fmt"
"./parse/parse"
)
func main() {
var examples = []string{
"1 2 3 4 5",
"100 50 25 12.5 6.25",
"2 + 2 = 4",
"1st class",
"",
}
for _, ex := range examples {
fmt.Printf("Parsing %q:
", ex)
nums, err := parse.Parse(ex)
if err != nil {
fmt.Println(err) // here String() method from ParseError is used
continue
}
fmt.Println(nums)
}
}
出力:
Parsing "1 2 3 4 5":
[1 2 3 4 5]
Parsing "100 50 25 12.5 6.25":
pkg: pkg parse: error parsing "12.5" as int
Parsing "2 + 2 = 4":
pkg: pkg parse: error parsing "+" as int
Parsing "1st class":
pkg: pkg parse: error parsing "1st" as int
Parsing "":
pkg: no words to parse