ソフトウェア設計をしていたら、仕様の違和感に気が付いて、仕様を変更した話を書きたいと思います。

違和感のある仕様

ある画面 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 への遷移が行われた場合には、何も追加の実装をする必要はありません。

一つ前の画面に戻るという実装がすでに存在するからです。

しかも、仕様書も修正する必要がありません。

さらに、もう一つ良いことがあります。

ボタンのラベルは「戻る」です。

戻るボタンをタップしたときに、二つ前の画面に戻ることってあるでしょうか?

まずないと思います。

まとめ

戻るボタンをタップした時は「一つ前の画面に戻ることが本質だ」と捉えたことで、

仕様、実装、ボタンラベル、拡張性、すべての辻褄が合いました。

以上、ソフトウェア設計中に、仕様の違和感に気が付いて、仕様を変更した話でした。