『GoLang』エラー処理

10394 ワード

GoはJavaやNETのようなtry/catch異常メカニズム:投げ異常操作は実行できません.しかし、defer-panic-and-recoverメカニズムがあります.
Goの設計者はtry/catchメカニズムの使用が氾濫しすぎて、底からより高いレベルに異常を投げて資源を消費していると思っています.彼らがGoに設計したメカニズムは異常を「捕捉」することもできるが,より軽量であり,(誤りを処理する)最後の手段とすべきである.
Goは普通のエラーをどのように処理しますか?関数およびメソッドでエラー・オブジェクトを一意または最後の戻り値として返すことによって、nilを返すとエラーは発生せず、プライマリ・コール(calling)関数は常に受信したエラーをチェックする必要があります.
エラーを処理し、関数にエラーが発生した場所でユーザーにエラー情報を返します.このように処理すれば、本当に問題が発生しても、あなたのプログラムは実行し続け、ユーザーに通知することができます.panic and recoverは、通常のエラーではなく、真の例外(予測不可能なエラー)を処理するために使用されます.
ライブラリ関数は、通常、プライマリ・コール関数にエラー・プロンプトを返す必要があります.
Goエラー条件のチェックと報告の慣習:
  • エラーが発生した関数は、2つの変数、1つの値、および1つのエラーコードを返します.後者が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から制御権を再取得し、終了プロセスを停止し、正常な実行に戻ります.panicrecover修飾の関数でのみ使用できます.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:明示的なパッケージ範囲外のrecover2)パケットの呼び出し元にエラー値を返す(panic())
    パケット内部、特に非導出関数に深いネストされた呼び出しがある場合、panicpanicに変換して、呼び出し元がなぜエラーを起こしたのかを伝えるのが実用的である(コードの可読性が向上する).
    次のコードはこの点をよく述べています.
    // 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