Go
golang

golangにおける独自エラー定義方法とエラーごとの処理分岐

More than 1 year has passed since last update.


内容


  • 独自エラーを生成する方法

  • 受け取り側で処理分岐できるエラーを作るには?


golangにおけるエラー


  • 下記インターフェースを実装した構造体


  • Error() は、エラーメッセージとしての文字列を出力する

type error interface {

Error() string
}


独自エラーを生成する方法


errors.New(text string) error


  • エラーメッセージを文字列で指定

  • 実体の構造体は、errors パッケージで定義されている errorString


fmt.Errorf(format string, a ...interface{}) error



  • fmt.Sprintf と同様、フォーマットでエラーメッセージを指定

  • 内部的には errors.New(fmt.Sprintf(format, a)) を呼び出しているだけ



    • errors.New と同じく errorString




error インターフェースを実装した独自構造体を定義する


  • 定義した独自構造体のインスタンスを生成してエラーとして使用する

  • ①②との違いは、以下



    • 型を変えられる : 型アサーションによりエラー処理を分岐できる


    • メッセージ以外のプロパティを持たせられる : エラーにステータスを持たせられる




(参考) pkg/errors パッケージ



  • errors.Newerrors.Errorf メソッドが用意されており、①②を同一パッケージで書ける

  • 内部的な処理は微妙に違うが、統一感がでるので個人的にはこちらが好き


受け取り側で処理分岐できるエラーを作るには?


Error() で出力される文字列を比較する 【アンチパターン】



  • error としてはエラーメッセージ(文字列)を返すメソッドがあるだけ

  • エラーメッセージを比較すれば、所望のエラーかどうか確認することができる

package main

import (
"errors"
"log"
)

var (
ErrMsg1 = "my error 1"
ErrMsg2 = "my error 2"
)

func main() {
if err := doError(); err != nil {
switch err.Error() {
case ErrMsg1:
log.Fatalf(ErrMsg1)
case ErrMsg2:
log.Fatalf(ErrMsg2)
}
}
}

func doError(i int) error {
switch i {
case 1:
return errors.New(ErrMsg1)
case 2:
return errors.New(ErrMsg2)
}
return nil
}


② エラーインスタンスを比較


  • パッケージのトップレベルにエラーインスタンスを作っておく


    • エラー生成側:事前生成したエラーインスタンスを返却する

    • エラー処理側:受け取ったエラーと事前生成したエラーインスタンスを比較する



package main

import (
"errors"
"log"
)

var (
ErrMyError1 = errors.New("my error 1")
ErrMyError2 = errors.New("my error 2")
)

func main() {
if err := doError(); err != nil {
switch err {
case ErrMyError1:
log.Fatalf("%#v", ErrMyError1)
case ErrMyError2:
log.Fatalf("%#v", ErrMyError2)
}
}
}

func doError(i int) error {
switch i {
case 1:
return ErrMyError1
case 2:
return ErrMyError2
}
return nil
}


③ 独自定義したエラー構造体の型を比較



  • error インターフェースを実装した独自構造体を定義する


    • エラー生成側:定義した構造体を生成して返却する

    • エラー処理側:受け取ったエラーインスタンスに型アサーションを実施する



package main

import (
"errors"
"log"
)

type MyError1 struct {}

func (e *MyError1) Error() string {
return "my error 1"
}

type MyError2 struct {}

func (e *MyError2) Error() string {
return "my error 2"
}

func main() {
if err := doError(); err != nil {
switch e := err.(type) {
case *MyError1:
log.Fatalf("%#v", e)
case *MyError2:
log.Fatalf("%#v", e)
}
}
}

func doError(i int) error {
switch i {
case 1:
return &MyError1{}
case 2:
return &MyError2{}
}
return nil
}


②と③の使い分け



  • 処理の分岐だけできれば良い場合


    • ②を使い、事前定義したインスタンスと比較する




  • 処理を分岐した上で、更に状態を取りたい場合


    • ③を使い、アサーションして構造体ごとの処理を呼ぶ



package main

import (
"errors"
"log"
)

type MyError1 struct {
Status int
}

func (e *MyError1) Error() string {
return "my error 1"
}

func main() {
if err := doError(); err != nil {
switch e := err.(type) {
case *MyError1:
// さらにこういった分岐をしたい場合は型アサーションにする 
switch e.Status {
case 404:
log.Fatalf("%#v, %#v", e, 404)
case 409:
log.Fatalf("%#v, %#v", e, 409)
}
}
}
}

func doError(i int) error {
return &MyError1{Status: 404}
}


④ 番外編 : Assert errors for behaviour, not type

Inspecting errors | Dave Cheney より


Assert errors for behaviour, not type



  • エラーをチェックするメソッド IsXXX(err error) bool を定義する方法

  • 上記②③の方法の場合、呼び出し元が特定の変数/型に依存することになる

  • この方法であればインターフェースへの依存で済む

package main

import (
"errors"
"log"
)

type temporary interface {
Temporary() bool
}

func (e *MyError1) Error() string {
return "my error 1"
}

func (e *MyError1) Temporary() bool {
return true
}

type MyError2 struct {}

func (e *MyError2) Error() string {
return "my error 2"
}

func IsTemporary(err error) bool {
te, ok := err.(temporary)
return ok && te.Temporary()
}

func main() {
if err := doError(); err != nil {
if IsTemporary(err) {
log.Fatalf("%#v", err)
}
}
}

func doError(i int) error {
switch i {
case 1:
return &MyError1
case 2:
return &MyError2
}
return nil
}


(おまけ) errorを作れるパッケージたち


errors


  • New(message string) error


fmt


  • Errorf(format string, args ...interface{}) error


pkg/errors


エラーの新規作成



  • New(message string) error



    • errors.New とほぼ同じ




  • Errorf(format string, args ...interface{}) error



    • fmt.Errorf をほぼ同じ




エラーのラッピング



  • WithMessage(err error, message string) error


    • 元の error をラップし、メッセージを付与




  • WithStack(err error) error


    • 元の error をラップし、スタックトレースを付与



  • Wrap(err error, message string) error


  • Wrapf(err error, format string, args ...interface{}) error


    • 元の error をラップし、メッセージとスタックトレースを付与




  • Cause(err error) error


    • ラップされた error を取り出す




(おまけ) panicを起こせるパッケージたち


ビルトイン関数


  • panic(message string)


log


panic系



  • Panic(v ...interface{})


    • Panic is equivalent to Print() followed by a call to panic().




  • Panicln(v ...interface{})


    • Panicln is equivalent to Println() followed by a call to panic().




  • Panicf(format string, v ...interface{})


    • Panicf is equivalent to Printf() followed by a call to panic().




(参考)fatal系



  • Fatal(v ...interface{})


    • Fatal is equivalent to Print() followed by a call to os.Exit(1).




  • Fatalln(v ...interface{})


    • Fatalln is equivalent to Println() followed by a call to os.Exit(1).




  • Fatalf(format string, v ...interface{})


    • Fatalf is equivalent to Printf() followed by a call to os.Exit(1).




参考