emahiro/b.log

Drastically Repeat Yourself !!!!

A little copying is better than a little dependency について考えてみる

これはたまに書いてる自分自身の思考の dump の一つです。
思考を吐き出してるだけなので、所々話が飛んでるかもしれないですがご愛嬌で。

サマリ

Go では A little copying is better than a little dependency.  という諺があり、これは依存 < 冗長を選択した方がいいケースがあることを示している。
プログラミングのお作法としての DRY を否定しているかのような諺であるが、実際にプロダクト開発をしていくにつれてこの諺の示すところの片鱗が少し見えてきたので、実体験を元にして文章にまとめてみる。

ref: https://go-proverbs.github.io

そもそも依存が嫌われるわけ

言ってしまえば、プロダクトを実際に作っている時には、DRY の原則に基づいた方が効率がいいと思う。
同じコードを2度以上書くのは面倒くさい(※1)

ただ、DRY の原則に則ってプロダクトのコードを書いてきたにもかかわらず、ある時からこの DRY の原則に従って生み出されたものに嫌気がさすタイミングがある。 それはどういうタイミングなのか、と考えると依存が大きく、かつ依存先の処理を理解しないと影響が範囲が読めずに「コードを簡単に変更できない」という事態に遭遇した時だ。

プロダクトは長く使われる中で改善され、改変されていく。刹那的なプロダクトや個人開発で自由気ままに開発しているプロダクトであればまぁ好きに書いてくれていいが、社会に対して何らかの価値を提供し、継続的に運用されているプロダクトは、常に何らかの改善や機能の追加がなされていく。

この改善や機能追加のタイミングで既存のコードに手を加える時、あるいは自分たちが使ってるライブラリや環境が古く、ソフトウェアが危険(セキュリティなど)に晒される可能性がある時に、既存のコードを変更したりライブラリを入れ替えたりする作業が必要になる。

これはソフトウェアエンジニアとして仕事をしていれば当然直面する課題であり、都度対応していかないといけない問題でもある。

しかしながら、一度コードを変更するときにその影響範囲について必ず調査をする。 なぜなら、いくらソフトウェアが危険に晒されてるからといって、プロダクトの振る舞いを変えたり、不具合を誘発しては事業そのものに影響が出る。あくまで事業に影響が出ないように、安全にプロダクトを改善するためにも、実際自分が今から変えようとしてるコードはどこに影響が出るのか?ということは必ず事前に調査し、明らかにしておく必要がある。

ちょっとしたコードの変更やロジックの修正、もしくはコードの削除、局所的にしか使われていないライブラリや共通処理の改善であれば話は早い。そこだけ入れ替えれば作業は完了する。

しかし、ちょっとした修正をするつもりが、該当箇所の依存先の実装に問題があったとしたらどうだろう?依存先のコードは、対応したかった箇所とは別の箇所でも使われているかもしれない。依存先の依存先でまた問題のある実装が見つかるかもしれない。 もしかしたら依存先のコードの修正はプロダクト全体に影響が出るところかもしれない。

プロダクトを開発する時には DRY の原則にしたがって実装したコードが、プロダクトを運用する時になって足枷になることは実は頻繁に発生する。

コピペを嫌って脳死で共通化した処理や、DRY の原則に基づいて開発当時は有用だった依存が運用フェーズになって重荷になる、要はプロダクトの中の情報のあり方やドメインが変わってきたときに脳死で DRY したり便利だと思っていた汎用処理がプロダクトの変更の重荷になる、ということが発生する。

※1. そもそもの DRY の原則とは「単なる重複をなくすことでない」ので誤認なきよう -> Don't repeat yourself - Wikipedia

意味が違うコピペ

繰り返しになるが、そう入っても同じ処理を何度も書くのは面倒くさい。それはそうだと思う。

意味で考えるにはコードそのものよりも HTML で考えてみるとわかりやすい。
(これは前に僕の尊敬するエンジニアから教わった例えをそのまま記載している。)

<h1>見出し1</h1>
<h2>見出し2_1</h2>
<h2>見出し2_2</h2>

という HTML の構造を考えた時に h2は同じことが書かれてるな? と思って

<h1>見出し1</h1>
<h2>
    見出し2_1
    見出し2_2
</h2>

こんな風に共通化する人はいないだろう?
DRY は極端にいえばこういうことすら肯定してしまう原則とも捉えられかねない。

h1 と h2 で意味するところが違う。それどころかページのレイアウトそのものにも影響を与えてしまう。

同じ処理だから脳死で共通化するとこういうことすら肯定してしまう。
重要なのは「共通化の意味」をちゃんと考えることだ。分かれていることに意味があるならそれは「意味のある冗長」であり、共通化するべきでない。

HTMLでの例えは多少乱暴かもしれないが、プロダクトのコードを書いてるとふとこうしたことを平気でしてしまいそうなくらい、共通化の悪魔の引力は強い。

処理として同じ文脈で同じ処理を書いているなら共通化するのが原則だが、文脈が違うなら共通化はするべきではない。

依存先の継続的な更新がある場合にのみ依存する

通化処理については上記に述べたとおりであるが、広く使われているOSSや自作ライブラリに依存するケースについて考えてみる。

自分はこれについては1つの見解を持っていて、見出しにあるように「継続的な更新が提供される場合のみ汎用的なライブラリに依存するべき」と考える。 継続的な更新が提供されない限りは、自作ライブラリすら作らない方がいい、という見解で、もし作るのであれば、プロダクト開発から離れてもなお、メンテナンスされ続ける体制を作っておくくらいのことをして初めて自作ライブラリをプロダクトに適用良いのだと思う。

おそらくこれを満たすものというのは、AWSGCP謹製のライブラリや、OSSとして広く定着しているものに限られる。個人で作ってるプロジェクト等のライブラリに依存するのは極力避けておくほうが、開発時期は負担が大きいかもしれないが、長期的な運用まで見据えると旨味が大きい。

結局大事なのは捨てやすさ

まぁそうは言ってもスケールする、継続するかもわからないプロダクトの開発において大事なのはスピードで、運用を見据えて多少冗長な実装をとって時間を食ってしまうことがそもそも事業的に許容されないケースというのは絶対に存在する。
※ 自作のライブラリのメンテナンスなんか考えてる人はさらに稀かもしれない。

実際に困った時になって「依存してるのが辛いな〜」とか「冗長に書いても良かったんじゃないかな〜」と思うことがほとんどだと思う。とすれば1つの観点として持っておきたいのは、依存や共通化はしてもいいけど、それが「どれだけ捨てやすい」のか?ということではないだろうか?

影響範囲を局所的にしておく、捨てやすい(or 入れ替えやすい)ように作っておくなど、プロダクトを開発するときに頭の片隅に置いておきたい観点は存在するので、それを引き出しとして持っておくことが大事なのかなと思う。

まとめ

当初書きたかった内容からは多少逸れてしまったが、自分のキャリアで大規模なプロダクトの開発を経験する中で Go の A little copying is better than a little dependency. という諺の言わんとしてるところの片鱗を考え始めるようになった。

ほんと、プロダクト開発は奥が深くて面白い。