Go エラー処理

Go 言語は組み込みのエラーインターフェースを通じて、非常にシンプルなエラー処理メカニズムを提供します。

Go 言語のエラー処理は、従来の例外処理メカニズムではなく、エラーを明示的に返す方式を採用しています。この設計によりコードロジックがより明確になり、開発者がコンパイル時または実行時にエラーを明確に処理しやすくなります。

Go のエラー処理は、主に以下のメカニズムを中心に展開されます:

  1. errorインターフェース:標準的なエラー表現。
  2. 明示的な戻り値:関数の戻り値を通じてエラーを返します。
  3. カスタムエラー:標準ライブラリまたはカスタムの方法でエラーを作成できます。
  4. panicとrecover:回復不能な重大なエラーを処理します。

error インターフェース

Go 標準ライブラリは、エラーの抽象を表す error インターフェースを定義しています。

error 型はインターフェース型であり、その定義は次のとおりです:

type error interface {
    Error() string
}
  • 実装errorインターフェース:任意のError()メソッドを実装した型はすべてエラーとして使用できます。
  • Error()そのメソッドはエラーを説明する文字列を返します。

errors パッケージを使用したエラー作成

コード作成中に error インターフェース型を実装することで、エラー情報を生成できます。

簡単なエラーを作成:

実例

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New("this is an error")
    fmt.Println(err) // 出力:this is an error
}

関数は通常、最後の戻り値でエラー情報を返します。errors.New を使用するとエラー情報を返せます:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

次の例では、Sqrt を呼び出すときに負の数を渡すと、non-nil の error オブジェクトを取得します。このオブジェクトを nil と比較すると結果は true になるため、fmt.Println(fmt パッケージは error を処理するときに Error メソッドを呼び出します)が呼び出されてエラーが出力されます。以下の呼び出しのサンプルコードをご覧ください:

result, err:= Sqrt(-1)

if err != nil {
   fmt.Println(err)
}

エラーの明示的な返却

Go では、エラーは通常関数の戻り値として返され、開発者は明示的にチェックして処理する必要があります。

エラーを明示的に返す:

実例

package main

import (
        "errors"
        "fmt"
)

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, errors.New("division by zero")
        }
        return a / b, nil
}

func main() {
        result, err := divide(10, 0)
        if err != nil {
                fmt.Println("Error:", err)
        } else {
                fmt.Println("Result:", result)
        }
}

出力:

Error: division by zero

カスタムエラー

カスタム型を定義することで、error インターフェースを拡張できます。

カスタムエラー型:

実例

package main

import (
        "fmt"
)

type DivideError struct {
        Dividend int
        Divisor  int
}

func (e *DivideError) Error() string {
        return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}

func divide(a, b int) (int, error) {
        if b == 0 {
                return 0, &DivideError{Dividend: a, Divisor: b}
        }
        return a / b, nil
}

func main() {
        _, err := divide(10, 0)
        if err != nil {
                fmt.Println(err) // 出力:cannot divide 10 by 0
        }
}

fmt パッケージとエラーフォーマット

fmtfmt パッケージはエラーのフォーマット出力をサポートしています:

  • %v:デフォルト形式。
  • %+v:対応している場合は、詳細なエラー情報を表示します。
  • %s:文字列として出力します。

実例

package main

import (
    "fmt"
)

// DivideError 構造体を定義
type DivideError struct {
    dividee int
    divider int
}

// `error` インターフェースを実装
func (de *DivideError) Error() string {
    strFormat := `
    Cannot proceed, the divider is zero.
    dividee: %d
    divider: 0
`

    return fmt.Sprintf(strFormat, de.dividee)
}

// `int` 型の除算を行う関数を定義
func Divide(varDividee int, varDivider int) (result int, errorMsg string) {
    if varDivider == 0 {
            dData := DivideError{
                    dividee: varDividee,
                    divider: varDivider,
            }
            errorMsg = dData.Error()
            return
    } else {
            return varDividee / varDivider, ""
    }

}

func main() {

    // 正常な場合
    if result, errorMsg := Divide(100, 10); errorMsg == "" {
            fmt.Println("100/10 = ", result)
    }
    // 除数がゼロのときにエラー情報を返します
    if _, errorMsg := Divide(100, 0); errorMsg != "" {
            fmt.Println("errorMsg is: ", errorMsg)
    }

}

上記のプログラムを実行すると、出力結果は次のとおりです:

100/10 =  10
errorMsg is:  
    Cannot proceed, the divider is zero.
    dividee: 100
    divider: 0


errors.Is と errors.As の使用

Go 1.13 以降、errorserrors パッケージは以下を導入しました:errors.Isとerrors.Asエラーチェーンを処理するために使用されます:

errors.Is

特定のエラーであるか、そのエラーによってラップされたものであるかをチェックします。

実例

package main

import (
        "errors"
        "fmt"
)

var ErrNotFound = errors.New("not found")

func findItem(id int) error {
        return fmt.Errorf("database error: %w", ErrNotFound)
}

func main() {
        err := findItem(1)
        if errors.Is(err, ErrNotFound) {
                fmt.Println("Item not found")
        } else {
                fmt.Println("Other error:", err)
        }
}

errors.As

エラーを特定の型に変換して、さらに処理できるようにします。

実例

package main

import (
        "errors"
        "fmt"
)

type MyError struct {
        Code int
        Msg  string
}

func (e *MyError) Error() string {
        return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg)
}

func getError() error {
        return &MyError{Code: 404, Msg: "Not Found"}
}

func main() {
        err := getError()
        var myErr *MyError
        if errors.As(err, &myErr) {
                fmt.Printf("Custom error - Code: %d, Msg: %s\n", myErr.Code, myErr.Msg)
        }
}

panic と recover

Go の panic は回復不能なエラーを処理するために使用され、recover は panic から回復するために使用されます。

panic:

  • プログラムをクラッシュさせ、スタック情報を出力します。
  • プログラムが実行を継続できない場合によく使用されます。

recover:

  • キャプチャpanic、プログラムのクラッシュを防ぎます。

実例

package main

import "fmt"

func safeFunction() {
        defer func() {
                if r := recover(); r != nil {
                        fmt.Println("Recovered from panic:", r)
                }
        }()
        panic("something went wrong")
}

func main() {
        fmt.Println("Starting program...")
        safeFunction()
        fmt.Println("Program continued after panic")
}

上記のコードを実行すると、出力結果は次のとおりです:

Starting program...
Recovered from panic: something went wrong
Program continued after panic
その他の拡張