はじめに
Goでテストコードを書いていたときの話だ。
「なんでわざわざ MustNewNode
なんて関数を作る必要があるんだ?」
最初は心の底からそう思っていた。
今回は、自分がその疑問にぶつかってから「なるほど!」と腑に落ちるまでの体験を書いていく。
これを読んだ人にもあの感動を味わってほしい。
1. 最初の疑問「NewNodeで良くない?」
テストコードの中でノードを作ろうとしたとき、こんなコードを見つけた。
mockNode: MustNewNode("node-123", "Q", "A", nil, nil)
「いやいや、MustNewNode
って何?普通にNewNode
使えば良くない?」
素直にそう思った。そこでこう書いてみた。
mockNode: domain.NewNode("node-123", "Q", "A", nil, nil)
2. コンパイルエラーで気づく
するとビルドが通らない。
エラーメッセージはこれ。
multiple-value domain.NewNode(...) in single-value context
「えっ、何これ?」
少し考えてハッとした。
NewNode
は戻り値が (Node, error)
になっている。
Goは明示的にエラーを返す文化。
つまりこう書かないといけない。
node, err := domain.NewNode(...)
if err != nil {
t.Fatal(err)
}
mockNode: node
3. いや、これテストに毎回書くのめんどくさすぎる問題
確かにこれが正しい書き方だ。
でもテストコードでこんな処理を何回も書くのは明らかに冗長だ。
せっかくテストコードで動作確認したいだけなのに、コード量が無駄に膨れる。
しかも「このパターンでエラーになるわけがない」って前提も多い。
4. なるほどMustNewNodeってそういうことか!
ここでやっとMustNewNode
の意図に気づいた。
「もしこの場面でエラーが出るなら、そもそもテストコードの書き方が間違っている」
だったら panic
で即座に気づけた方が良い。
だからこういう実装になっている。
func MustNewNode(...) domain.Node {
node, err := NewNode(...)
if err != nil {
panic(err)
}
return node
}
5. Goらしい合理的な割り切りに感動した
この考え方、めちゃくちゃGoっぽい。
「本番コードでは安全に」
「テストコードではスピード優先で」
その切り分けが潔くて合理的。
しかも、標準パッケージでもこの思想が貫かれている。
regexp.MustCompile()
template.Must()
time.MustParse()
まとめ
今回の出来事で、Goの哲学が少し身体に染み込んだ気がした。
最初は「わざわざMustなんていらない」と思った。
でも実際に手を動かして、エラーを起こして、初めて腑に落ちた。
こういう「試行錯誤して初めて見える景色」があるのがプログラミングの面白さだと思う。
この記事が、同じ疑問を持った誰かの助けになれば嬉しい。