Go 2 Draft DesignsのError Handlingについて
はじめに
Go 2 Draft Designs フィードバック会に参加してきたのですが、その中のエラーハンドリングの話について思ったことを書きます。
本家のGo 2 Draft Designsでは、handle
とcheck
によって次のようにエラーハンドリングができるようになると書かれています。
func process(user string, files chan string) (n int, err error) { handle err { return 0, fmt.Errorf("process: %v", err) } // handler A for i := 0; i < 3; i++ { handle err { err = fmt.Errorf("attempt %d: %v", i, err) } // handler B handle err { err = moreWrapping(err) } // handler C check do(something()) // check 1: handler chain C, B, A } check do(somethingElse()) // check 2: handler chain A }
個人的にエラーハンドリングを簡単にかけるようにすることは賛成ですが、このやり方はよくないと思ったので別の方法を考えました。
handle and checkのつらいところ
上のコードに書かれたコメントにあるようにhandle
にはスコープが存在し、1つの関数内に複数のhandle
が書けるようになっています。
さらにhandle
はreturn
を書かなければerr
を上書きできるようになっており、check 1のコメントがある部分ではC, B, Aのhandle
が実行されます。
ただしcheck 2のコメントの部分ではAのhandle
のみが実行されるようになっており、エラーが一体どのhandle
で処理されるのかを意識しながらコーディングしなくてはなりません。
考えた方法
スコープを考えるのがつらいのであれば、関数スコープ外でhandle
を定義できればよいのです。
なのでhandler
というものを定義できるようにすることを考えました。
// handlerと書くことでエラーハンドラーを定義できる handler ParseError(err error) error { return errors.Wrap(err, “parse error“) } // クロージャーと同じようにhandlerを返すfuncを定義できる func WithMessage(message string) handler(err error) { return handler(err error) error { returns errors.Wrap(err, message) } } // 常にnilを返すことでエラーを無視できる handler Ignore(_ error) error { return nil } type MultiError struct { Errors []error } // structのメソッドとしても定義できる handler (me *MultiError) Append(err error) error { me.Errors = append(me.Errors, err) return nil } func (me *MultiError) Error() string { return "multi error" } func doX() error { // checkはhandlerを受け取り、関数の返り値のerrでhandlerを呼び出す。handlerの返り値がnilなら処理を継続、そうでなければその場でreturnする。 i1 := check ParseError strconv.ParseInt(“123”, 10, 64) i2 := check WithMessage(“parse error”) strconv.ParseInt(“123”, 10, 64) i3 := check Ignore strconv.ParseInt(“123”, 10, 64) // structに生やすことで複数のエラーを簡単にまとめることもできる。 me := &MultiError{} i4 := check me.Append strconv.ParseInt(“123”, 10, 64) i5 := check me.Append strconv.ParseInt(“456”, 10, 64) return me }
公式のDraftと大きな違いはありません。
handler
というfunc
のようなものを定義できるようにするだけです。
handler
はfunc(err error) error
という形で固定です。
check
ではhandler
を受け取り、関数の返り値のerrがerr != nil
の場合のみhandler
を呼び出します。
呼び出したhandler
の返り値がerr != nil
の場合は、返ってきたerrをその場でreturnします。
nil
だった場合はなにもせずにそのまま処理を継続します。
考え方としては、特別なinterfaceであるerror
と同じように特別なfuncであるhandler
を定義可能にしようというイメージです。
なお、このhandler
を採用する場合、handler
型が書かれているところではエラーハンドリングが行われることを理解できるので、check
という構文自体を消して次のように書くことも可能になると思います。
i1 := ParseError strconv.ParseInt(“123”, 10, 64)
check
を入れるかどうかは見やすさや、コンパイラの実装がやりやすいほうに倒せばいいかなと思います。
追記
"Use functions as an error handler, Add syntactic sugar to remove duplicated if statement"
というタイトルで Go2ErrorHandlingFeedback · golang/go Wiki · GitHub にFeedbackを書いておいた。
Go Fridayで話したらhandler
構文なくしてもいいんじゃないかということを言われたので、提案したproposalではhandler
をfuncにしてcheck
のシンタックスシュガーのみを採用することにした。
GoにおけるDI
Go4 Advent Calendar 2017 12日目の記事です。
続きを読む