はじめに
goをさわって数ヶ月ですが、雰囲気では書けていたものの
errorやエラーハンドリングについてはもやもやしたままだったので自分理解メモの②
関連
この記事の関連です。
- goのエラーについて
- 一番簡単なエラーハンドリング
- エラーハンドリングのパターン
- パターン1: errorの文字列で
- パターン2: errors.Newのインスタンスで
- パターン3: カスタムエラーのインスタンスで
- パターン4: カスタムエラーの型で
1. goのエラーについて
goのエラーについては以下に記載してみました。
【go】golangのエラー処理メモ - ①. errorとError型とカスタムErrorと
あえて項目にするまでもなかったけど前提として載せておきます
2. 一番簡単なエラーハンドリング
goは例外というモノがなく、
基本的には関数を呼び出した結果がエラーな場合はその場で処理します。
簡単なエラーハンドリング
その場で処理しちゃう例の一番簡単なものです。
サンプル
サンプルコード
package main
import (
"errors"
"fmt"
"os"
)
func doSomething() error {
return errors.New("doSomething is error.")
}
func main() {
err := doSomething()
if err != nil {
fmt.Println("main is failed")
os.Exit(1)
}
fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample01/main.go
main is failed
exit status 1
エラーを無視する
また、エラーでも問題ない場合は_
や単に何も受け取らないことで無視する事もできます。
サンプル
サンプルコード
package main
import (
"errors"
"fmt"
)
func doSomething() error {
return errors.New("doSomething is error.")
}
func doHoge() {
err := doSomething()
if err != nil {
fmt.Println("doHoge is failed")
return
}
fmt.Println("doHoge is success")
}
func doFuga() {
_ = doSomething()
fmt.Println("doFuga is success")
}
func doPiyo() {
doSomething()
fmt.Println("doPiyo is success")
}
func main() {
doHoge()
doFuga()
doPiyo()
fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample02/main.go
doHoge is failed
doFuga is success
doPiyo is success
main is success
3. エラーハンドリングのパターン
上記のような簡単なエラーハンドリングでも事足りる事はあります。
しかし、
実際は下位のmethodで発生したエラーをハンドリングしては上位に返して…
を繰り返すと困る事がでてきます。
自分は以下のような事が困りました。
- 最上位でハンドリングで出力したものの根本原因がどこかわからない
- エラーによって分岐したいがどのようにするのがベストプラクティスかわからない
エラーハンドリングのパーターンとしては以下のサイトが非常に参考になります。
参考サイトを参考に、
エラーハンドリングは以下のようなパターンで行う事ができます。
エラーハンドリングのパターン
- パターン1: errorの文字列で
- パターン2: errors.Newのインスタンスで
- パターン3: カスタムエラーのインスタンスで
- パターン4: カスタムエラーの型で
バッドノウハウなパターンも含まれてますが、
さっと書く使い捨てのscriptだったりではバッドノウハウパターンでも無いよりは良いかな、という印象です。
4. パターン1: errorの文字列で
errors.New
なりfmt.Errorf
の文字列を受け取り側で比較するというものです。
単純ですがバッドノウハウとされているパターンです。
サンプル
サンプルコード
package main
import (
"errors"
"fmt"
"os"
)
const (
ERROR_MSG_01 = "doSomething is error. b is true"
ERROR_MSG_02 = "doSomething is error. b is false"
)
func doSomething(b bool) error {
if b {
return errors.New(ERROR_MSG_01)
} else {
return errors.New(ERROR_MSG_02)
}
return nil
}
func main() {
err := doSomething(true)
if fmt.Sprintf("%s", err) == ERROR_MSG_01 {
fmt.Println("ERROR: ERROR_MSG_01に応じた処理を行う")
os.Exit(1)
} else if fmt.Sprintf("%s", err) == ERROR_MSG_02 {
fmt.Println("ERROR: ERROR_MSG_02に応じた処理を行う")
os.Exit(1)
}
fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample03/main.go
ERROR: ERROR_MSG_01に応じた処理を行う
exit status 1
困る点
ハンドリング後、さらに呼び出し元に返そうとするととたんに困ります…
if fmt.Sprintf("%s", err) == ERROR_MSG_01 {
return fmt.Errorf("ERROR: err=%+v", err)
}
5. パターン2: errors.Newのインスタンスで
errors.Newのインスタンスで比較するパターンです。
osパッケージなんかでも使われてたりします
https://golang.org/src/os/error.go#L11
サンプル
サンプルコード
package main
import (
"errors"
"fmt"
"os"
)
const (
ERROR_MSG_01 = "doSomething is error. b is true"
ERROR_MSG_02 = "doSomething is error. b is false"
)
var (
ERROR_01 = errors.New(ERROR_MSG_01)
ERROR_02 = errors.New(ERROR_MSG_02)
)
func doSomething(b bool) error {
if b {
return ERROR_01
} else {
return ERROR_02
}
return nil
}
func main() {
err := doSomething(false)
if err == ERROR_01 {
fmt.Println("ERROR: インスタンスERROR_01に応じた処理を行う")
os.Exit(1)
} else if err == ERROR_02 {
fmt.Println("ERROR: インスタンスERROR_02に応じた処理を行う")
os.Exit(1)
}
fmt.Println("main is success")
}
コード自体はだいぶマシになった気はしてきます
出力
$ go run src/go-error-handling/sample04/main.go
ERROR: インスタンスERROR_02に応じた処理を行う
exit status 1
困る点
パターン1と同様ですがハンドリングの結果、上位にエラーを返すときに困ります。
そのまま返すとどこでエラーだったのかわかりずらくなり、
fmt.Errorfなどで新規にインスタンスを作ってしまうと別物になってしまいます。
if err == ERROR_01 {
return ERROR_01
}
if err == ERROR_01 {
return fmt.Errorf("ERROR: err=%+v", ERROR_01)
}
6. パターン3: カスタムエラーのインスタンスで
【go】golangのエラー処理メモ - ①. errorとError型とカスタムErrorと
でも触れたカスタムエラーを定義してそのインスタンスでハンドリングするパターンです。
1つのカスタムエラーを使い回してますが、カスタムエラー自体を複数用意しても良いです。
サンプル
サンプルコード
package main
import "fmt"
const (
ERROR_MSG_01 = "doSomething is error. b is true"
ERROR_MSG_02 = "doSomething is error. b is false"
)
type MyError struct {
Msg string
Code int
}
func (err *MyError) Error() string {
return fmt.Sprintf("%s, %d", err.Msg, err.Code)
}
var (
MyError_01 = &MyError{Msg: "MyError_001 is occur", Code: 30001}
MyError_02 = &MyError{Msg: "MyError_002 is occur", Code: 30002}
)
func doSomething(b bool) error {
if b {
return MyError_01
} else {
return MyError_02
}
return nil
}
func main() {
err := doSomething(false)
switch err {
case MyError_01:
fmt.Println("ERROR: インスタンスMyError_01に応じた処理")
fmt.Printf("ERROR: %+v\n", err)
case MyError_02:
fmt.Println("ERROR: インスタンスMyError_02に応じた処理")
fmt.Printf("ERROR: %+v\n", err)
}
fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample05/main.go
ERROR: インスタンスMyError_02に応じた処理
ERROR: MyError_002 is occur, 30002
main is success
困る点
パターン2に同じ
7. パターン4: カスタムエラーの型で
カスタムエラーの型でハンドリングするパターンです。
err.(type)
という記述をしますが、
これはerrorがinterface型のために行えるらしいです。(知らなかった)
また、これをConversion構文というらしいです。
https://golang.org/ref/spec#Conversions
サンプル
サンプルコード
package main
import "fmt"
type MyError_01 struct {
Code int
}
func (err *MyError_01) Error() string {
return fmt.Sprintf("this is MyError_01")
}
type MyError_02 struct {
Code int
}
func (err *MyError_02) Error() string {
return fmt.Sprintf("this is MyError_02")
}
func doSomething(b bool) error {
if b {
return &MyError_01{Code: 30001}
} else {
return &MyError_02{Code: 30002}
}
return nil
}
func main() {
err := doSomething(false)
switch e := err.(type) {
case *MyError_01:
fmt.Println("ERROR: MyError_01の型に応じた処理")
fmt.Printf("ERROR: err=%+v, e=%+v, code=%d\n", err, e, e.Code)
case *MyError_02:
fmt.Println("ERROR: MyError_02の型に応じた処理")
fmt.Printf("ERROR: err=%+v, e=%+v, code=%d\n", err, e, e.Code)
}
fmt.Println("main is success")
}
出力
$ go run src/go-error-handling/sample06/main.go
ERROR: MyError_02の型に応じた処理
ERROR: err=this is MyError_02, e=this is MyError_02, code=30002
main is success
困る点
これまでの中では一番すっきりした感があります。
しかし、参考サイトまんまの受け売りですが、
このままerrorを上に上に伝搬させていくと
上位ロジックが全てのerrorを把握している必要がある、という感じ。
ではどうするのか
pkg/errors
を使うと良いらしい
が、長くなったのでまた次の記事にします。
これもまた参考サイトが非常に参考になりました!
おわり
errorについても奥が深い!\(^o^)/