Goを使ってイテレータっぽい処理を書くとき、構造体に諸々の情報を詰めて、 Next()
で次のデータを読み込むみたいな処理を書くことが多いと思います。その際のエラー報告をどうするのが良いんだろうという内容です。
こうすると良さそう
Next()
が返すのは次のレコードがあるかどうかを判別するためのbool
のみとし、エラーの報告は別途それ用のフィールドと関数を生やすのが良さそうです。
type Hoge struct {
...
err error
}
...
func (h *Hoge) Next() bool {
// 次のレコードとかに移動する処理
// エラーがあったらh.errに入れる
}
func (h *Hoge) Err() error {
return h.err
}
なぜこれが良さそうか
Goのif文では、;
を区切りとすることで条件部分に複数の文を書くことができます:
if data, ok := Something(); ok {
...
}
しかし、for文ではこの書き方はできません:
// うまく認識されない
for data, ok := Something(); ok {
...
}
ということはつまり、データを持ってきたうえでエラーチェックをしてループを回すといった処理ができないということになります。
こういう書き方ができないというわけです:
for hasNext, err := something.Next(); hasNext && err != nil {
...
}
いい感じにfor文の条件式の中で条件を組み立てることができないため、Next()
の戻り値にerrを含めると色々面倒な感じになります。
素直にNext()
はboolのみを返すことにして、エラーチェックは別で行うことにすればシュッと書けます。
Goのライブラリの実装を見てみる
実際、Goの標準ライブラリでも同じような書き方をされることが多いようです。
bufio.Scanner
それぞれこんな感じのシグネチャになっています。
func (s *Scanner) Scan() bool
func (s *Scanner) Err() error
参考:https://pkg.go.dev/bufio#Scanner
database/sql
sqlでレコードを取り出すやつですね。同じような書き方です。
func (rs *Rows) Next() bool
func (rs *Rows) Err() error
参考:https://pkg.go.dev/database/sql#Rows
ややつらそうな点
ここで書いたものと同じ形でエラー報告するように強制するのが難しそうだなと感じました。
Err()
が何を意図して作られているのかは、インタフェース定義にコメントなんかで残すしかありませんし、このあたりの気持ちを察してもらうのは認知コストが大きそうに思えます。
インタフェースのシグネチャだけでいい感じに気持ちが伝わる書き方できないかなぁ…
それとも私がこれまで知らなかっただけで、Gopherの皆様の中ではこの書き方は割と常識なんですかね…