Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 3 years have 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).

参考

ryskiwt
IoTプラットフォームを手がけるベンチャー企業でVPoPをやっています。 Golang/Java/Python/JavaScript、統計学/時系列解析/信号処理/移動体通信工学
aptpod
"日本のIoT、M2Mを牽引するテクノロジーカンパニーです。⾃動⾞やロボット・産業機械などから短周期に発生する制御・センサーデータをモバイル・インターネット網を介し、⾼速・大容量且つ安定的に、そして双方向に伝送し、回収データの可視化・解析や遠隔制御を実現するプロダクト開発を行っています。"
https://www.aptpod.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away