内容
- 独自エラーを生成する方法
- 受け取り側で処理分岐できるエラーを作るには?
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.New
とerrors.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).
参考
- Go言語のエラーハンドリングについて - Qiita
- 【go】golangのエラー処理メモ - ②. 例外はないがエラーハンドリングはできるよ(インスタンスや型でハンドリング) - tweeeetyのぶろぐ的めも
- Javaの検査例外は、呼び出し側の責任でない異常系 - Qiita
- Go言語でエラーを書くときに気をつけること - taknb2nchのメモ
- DSAS開発者の部屋:Go ではエラーを文字列比較する?という話について
- loggingについて話そう - Qiita
- Gocon Spring 2016
- errors - The Go Programming Language
- fmt - The Go Programming Language
- log - The Go Programming Language
- errors - GoDoc