LoginSignup
54
46

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-10-15

内容

  • 独自エラーを生成する方法
  • 受け取り側で処理分岐できるエラーを作るには?

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).

参考

54
46
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
46