11193


Go言語を初めて1週間ほどですが、エラーハンドリングの方法について良い方法が思いつかなかった為、質問させて頂きます。  
今回の例はjsonデコードですが、json#UnmarshalJSONが問題なのではなく、複数のメソッドを呼び出すメソッドを記述した際には普遍的に起こる問題かと思います。

## 目的

以下に示すコードのように、独自クラスTaskに対してjson.Unmarshalerインターフェイスを実装しようとしています。  
なお、今回の問題とは直接関係ありませんが、m["original"]の内容によってt.Originalの型を変更したい為、Original側にUnmarshalJSONを実装することはできません。

[code]

    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
    }
    
[/code]

## 問題点

json.UnmarshalやoriginalUnmarshalではエラーが発生する可能性があるため、返り値としてerrorを返しています。この部分のエラーハンドリングを考えたところ、以下のように`if
err != nil { return err }`だらけのコードとなってしまいました。

[code]

    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
    }
    
[/code]

## 調査・対応

同じような問題で悩んでいる人を探した所、公式を含む以下のサイトが引っかかりました。  
<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>

これを元に汎用的なクラスを実装したところ、クロージャだらけになったものの一応実装自体はできました。

[code]

    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()
    }
    
[/code]

しかし、「目的」で示したコードに対してややこしくなってしまっており、あまり良いコードとも思えません。  
こういった場合の定番の方法は何かあるのでしょうか？


CC BY-SA 3.0
2015-06-09T07:13:34.300
11167
2015-06-10T14:21:01.323
2728
question
go
Go言語において複数のメソッドを呼ぶ際のエラーハンドリングでifが乱立してしまう


汎用化しようとするのが誤りでしょう。上げられている資料 <http://jxck.hatenablog.com/entry/golang-error-
handling-lesson-by-rob-pike> に基づくならば、以下のような実装 `json2` にとどめるべきです。

[code]

    type json2 struct {
      err error
    }
    
    func (j *json2) unmarshal(data []byte, v interface{}) error {
      if j.err != nil {
        return j.err
      }
      j.err = json.Unmarshal(data, v)
      return j.err
    }
    
    func (j *json2) originalUnmarshal(data []byte, v *Original) error {
      if j.err != nil {
        return j.err
      }
      j.err = originalUnmarshal(data, v)
      return j.err
    }
    
[/code]

汎用化する仕組み/フレームワークをあらかじめ作るのではなく、 **必要な物だけを必要なときに書く** ことで、維持したいロジックをスッキリさせる方が
golang 的です。どうしても汎用化したいのであれば、 `json2`
に相当するコードを自動生成(generate)するような方法を模索すべきではないでしょうか。

なお、この `json2` を使うことで、元のコードは以下のように書き換わります。

[code]

    func (t *Task) UnmarshalJSON(d []byte) error {
      m := make(map[string]json.RawMessage)
      j := &json2{}
      j.unmarshal(d, &m)
      j.unmarshal([]byte(m["name"]), &t.Name)
      j.unmarshal([]byte(m["trigger"]), &t.Trigger)
      j.unmarshal([]byte(m["description"]), &t.Description)
      j.unmarshal([]byte(m["filter"]), &t.Filter)
      j.originalUnmarshal([]byte(m["original"]), &t.Original)
      return j.err
    }
    
[/code]

* * *

### 余談

`json2` というよりは意味合いから `unmarshalContext` とすべきでした。まぁ説明のため短い名前を採用したということで、ご容赦を。


CC BY-SA 3.0
2015-06-09T16:44:58.527
11193
2015-06-10T14:21:01.323
2015-06-10T14:21:01.323
208
208
11167
answer