しばらく前に Redundancy vs dependencies: which is worse?[1] という記事を読んで感銘を受けた。コードのコピーアンドペーストによって発生する問題より、他人のコードへ依存することによって発生する問題の方が大きいんじゃないの?といった内容だ。長めだけど示唆に富むので一読をおすすめしたい。
コードの冗長性が悪いということはよく知られている。コードのコピーアンドペーストは分かりやすい問題だ。コピペしてバグがあったら2箇所直さないといけないぞ!と言われるとその通りとしか言いようがない。DRY (Don't Repeat Yourself) なんて名前までついている。
一方、依存性の問題について語られることはそれほど多くないと思う。依存性が引き起こす問題はコピペの問題よりもわかりにくいし、依存性を増やす=既存のコードを使う、ことはむしろ好ましいこととして語られることも多い。車輪の再発明をしないで既存のライブラリをどんどん活用しようぜ!本当にそうだろうか?
私の経験からすると、コピペされたコードはたまに見かけるが、そのせいでえらい目にあったことはない。一方、よく考えずに追加された依存性のせいでえらい目にあったことは何度もある。例えばこんな感じだ:
開発中のアプリケーションで、オフラインモードをサポートすることになった。次期バージョンの目玉機能だ。このためにはデバイスがネットに接続されているかを調べる必要があるのだが、これが何やら難しいようだ。ネットを検索してみると、これをやってくれるちょうどいいライブラリが見つかった。しかも、このライブラリのすばらしいところは使うのが超簡単ということだ。何しろ IsConnectedToNetwork() という関数を呼ぶだけなのだ。これぞまさに求めていたものだ!
オフラインモードの開発は順調に進んだ。 IsConnectedToNetwork() を適切なところで呼んで、処理を分けていけばいい。が、ひと通り動くようになった頃、エンジニアの一人が不吉な問題を見つけた。アプリを使っているとオンラインだろうとオフラインだろうと、たまにUIが一瞬固まるのだ。再現性はない。ログを大量に仕込んで必死になって調べてわかったのは、IsConnectedToNetwork() で10〜500ミリ秒くらい固まっているということだ。なんで?これ true か false か返すだけの単純な関数じゃないの?
IsConnectedToNetwork() の実装を調べて分かったのは、この一見、単純そうな関数は内部的に、OSのネットワークサブシステムに同期的にIPCを投げているということだ。なんらかの理由で IPCの返事が遅いとその間、UIが固まってしまうというわけだ。なんたること!こういうIPCは非同期に投げるのが常識だろう!頭を抱えながら、件のライブラリのサイトを見ると、新しいバージョンでは非同期呼び出しに対応しているとのこと。やれやれ、新しいバージョンを使えばいいってことか。ん?ビルドできないぞ。うわ、この新しいバージョン、非同期IPCのために、くそでかいなんちゃらフレームワークライブラリに依存してるってわけ?そんなでかいのうちのアプリに入れられねーぞ!
結局、IsConnectedToNetwork() の非同期版を自分たちで書き直すことになった。しかも、IsConnectedToNetwork() を呼んでいる無数のクライアントコードを、すべて非同期呼び出しに書き直す必要があるのだ。同期的なコードを非同期に変えるのはかなり厄介な変更だ。既存のライブラリを使って時間を節約するつもりが逆に大損してしまった。こうなるのなら、最初から自分たちで書いておけばよかったよ。とほほ。
上の話はフィクションだが、これに近いような失敗は何度も見た。他にも、依存したライブラリの開発が終わってしまってメンテ不在になるとか、新しいバージョンに上げるたびにバグが混入するとか、バージョン2.0からAPIが激変する、などなど、厄介なことはいろいろ起きる。依存性を取り除く作業は大抵、難航を極めるものだ。
そんなこともあって、依存性を増やすことには慎重になった。NIH (Not Invented Here) 症候群と言われようと、自分たちで書いてしまった方がいい場合は多いものだ。明確な基準はないけれども。
[1] http://www.yosefk.com/blog/redundancy-vs-dependencies-which-is-worse.html
p.s.
依存性に関する議論は多くないよなーと思ったけど、Joel on Software で扱われていた。昔読んだはずなんだけど忘れていた。 http://www.joelonsoftware.com/articles/fog0000000007.html