Go言語において複数のメソッドを呼ぶ際のエラーハンドリングでifが乱立してしまう
Go言語を初めて1週間ほどですが、エラーハンドリングの方法について良い方法が思いつかなかった為、質問させて頂きます。
今回の例はjsonデコードですが、json#UnmarshalJSONが問題なのではなく、複数のメソッドを呼び出すメソッドを記述した際には普遍的に起こる問題かと思います。
目的
以下に示すコードのように、独自クラスTaskに対してjson.Unmarshalerインターフェイスを実装しようとしています。
なお、今回の問題とは直接関係ありませんが、m["original"]の内容によってt.Originalの型を変更したい為、Original側にUnmarshalJSONを実装することはできません。
func (t *Task) UnmarshalJSON(d []byte) error {
m := make(map[string]json.RawMessage)
err = json.Unmarshal(d, &m)
if err != nil {
return err
}
json.Unmarshal([]byte(m["name"]), &t.Name)
json.Unmarshal([]byte(m["trigger"]), &t.Trigger)
json.Unmarshal([]byte(m["description"]), &t.Description)
json.Unmarshal([]byte(m["filter"]), &t.Filter)
originalUnmarshal([]byte(m["original"]), &t.Original)
return nil
}
問題点
json.UnmarshalやoriginalUnmarshalではエラーが発生する可能性があるため、返り値としてerrorを返しています。この部分のエラーハンドリングを考えたところ、以下のようにif err != nil { return err }
だらけのコードとなってしまいました。
func (t *Task) UnmarshalJSON(d []byte) error {
m := make(map[string]json.RawMessage)
err = json.Unmarshal(d, &m)
if err != nil {
return err
}
err = json.Unmarshal([]byte(m["name"]), &t.Name)
if err != nil {
return err
}
err = json.Unmarshal([]byte(m["trigger"]), &t.Trigger)
if err != nil {
return err
}
err = json.Unmarshal([]byte(m["description"]), &t.Description)
if err != nil {
return err
}
err = json.Unmarshal([]byte(m["filter"]), &t.Filter)
if err != nil {
return err
}
err = originalUnmarshal([]byte(m["original"]), &t.Original)
if err != nil {
return err
}
return nil
}
調査・対応
同じような問題で悩んでいる人を探した所、公式を含む以下のサイトが引っかかりました。
https://groups.google.com/forum/#!topic/Golang-Nuts/sFgUpNvklmo
http://blog.golang.org/errors-are-values
http://jxck.hatenablog.com/entry/golang-error-handling-lesson-by-rob-pike
これを元に汎用的なクラスを実装したところ、クロージャだらけになったものの一応実装自体はできました。
type MultipleFunction struct {
err error
}
func (m *MultipleFunction) Execute(f func() error) {
if m.err != nil {
return
}
m.err = f()
}
func (m *MultipleFunction) Error() error {
return m.err
}
func (t *Task) UnmarshalJSON(d []byte) error {
var err error
m := make(map[string]json.RawMessage)
mf := MultipleFunction{}
mf.Execute(func() error { return json.Unmarshal(d, &m) })
mf.Execute(func() error { return json.Unmarshal([]byte(m["name"]), &t.Name) })
mf.Execute(func() error { return json.Unmarshal([]byte(m["trigger"]), &t.Trigger) })
mf.Execute(func() error { return json.Unmarshal([]byte(m["description"]), &t.Description) })
mf.Execute(func() error { return json.Unmarshal([]byte(m["filter"]), &t.Filter) })
mf.Execute(func() error { return originalUnmarshal([]byte(m["original"]), &t.Original) })
return mf.Error()
}
しかし、「目的」で示したコードに対してややこしくなってしまっており、あまり良いコードとも思えません。
こういった場合の定番の方法は何かあるのでしょうか?