ソフトウェア設計をしていたら、仕様の違和感に気が付いて、仕様を変更した話を書きたいと思います。
違和感のある仕様
ある画面 C は、画面 A と画面 B という二つの画面から遷移可能な画面でした。
画面 C には、戻るボタンがありました。
戻るボタンをタップすると、
「画面 A から遷移した場合は A に戻り、画面 B から遷移した場合は B に戻る」
という仕様でした。
この、何の変哲もない普通の仕様に、実は違和感が潜んでいました。
【補足】
今回の話は、非常に些細な点をとりあげます。
そのため、この話で出てくる例自体は、実際にはほぼ無害です。
しかし、簡単な例だからこそ、
どのようにソフトウェアが壊れていくのかを
誰が読んでもわかる例になっています。
それを知るきっかけとなるように、この記事を残します。
違和感のある仕様通りに実装した場合
Android で画面遷移を実装する場合は、 Navigation Component というライブラリを使用することが一般的です。
Navigation には、
一つ前の画面に戻る 場合には、
NavController.navigateUp() という関数が使えます。
一方で、 指定した画面まで戻る 場合には、
NavController.popBackStack(route = /* 戻りたい画面 */, inclusive = false)
という関数が使えます。
仕様では、「画面 A から遷移した場合は A に戻り…」とあるので、後者の関数を使って実装します。
前者の関数を使用して実装しても、現時点では同じ動作になりますが、将来的には同じとは限りません。
例えば、 A -> D -> C という画面遷移になった場合には、
前者の実装では D に戻りますが、後者の実装では A に戻ります。
現時点の動作が同じなら、前者でも後者でもいいのではないか?
はい。この例のように簡単なケースの場合は、前者でも後者でも、あまり害にはなりません。
ただし、基本的には、 仕様書に記載のある通りに実装することが大切 だと私は思っています。
これらの些細な実装方法の違いでも、積み重なっていけば、コードを読むときの認知負荷となります。
スムーズなコードリーディングを阻害することで、コードが追えなくなるときが来る可能性があります。
解決策
このケースの場合、どうするのが最適なのかを考えました。
その結果、仕様を見直すという判断になりました。
見直し後の仕様では
C 画面の戻るボタンをタップしたときは、一つ前の画面に戻る
とします。
そして、実装も
NavController.navigateUp() という関数を使用して実装します。
こうすることで、仕様と実装が一致し、今後、新たな画面 D から C への遷移が行われた場合には、何も追加の実装をする必要はありません。
一つ前の画面に戻るという実装がすでに存在するからです。
しかも、仕様書も修正する必要がありません。
さらに、もう一つ良いことがあります。
ボタンのラベルは「戻る」です。
戻るボタンをタップしたときに、二つ前の画面に戻ることってあるでしょうか?
まずないと思います。
まとめ
戻るボタンをタップした時は「一つ前の画面に戻ることが本質だ」と捉えたことで、
仕様、実装、ボタンラベル、拡張性、すべての辻褄が合いました。
以上、ソフトウェア設計中に、仕様の違和感に気が付いて、仕様を変更した話でした。