2016年12月26日
コードの半減期とテセウスの船
(2016-12-05)by Erik Bernhardsson
本記事は、原著者の許諾のもとに翻訳・掲載しております。
プロジェクトが発展する際は、単純に新しいコードが古いコードの上に追加されているのでしょうか。もしくは、時間をかけて徐々に古いコードが新しいコードに置き換えられているのでしょうか。これを解明するために、手ごわい GitPython プロジェクトの助けを借りて、Gitプロジェクトを分析する 簡単なプログラム を構築してみました。履歴を年ごとに振り返り、 git blame
を実行してみようと思ったのです(この処理を多少でも速くすることは簡単ではないと分かりました。しかし、ファイルのキャッシングを便宜的に含ませることや、変更された点を履歴から見つけること、 git diff
を使って変更したファイルを無効にすることなどの詳細を、いつかお伝えします)。
頭がさえている時に、 テセウスの船 をダサくもじって、 “テセウスのGit” と名付けました。私は父親になって、ひどいダジャレを作れるようになったのです。テセウスの船は哲学的なパラドックスを示しています。ある船を何百年も使い続け、その間に部品を少しずつ交換した場合、全ての部品が交換されても同じ船といえるのか、という疑問を投げかける言葉です。
テセウスとアテネの若者がクレタ島から帰還した際、乗っていた船には30本の櫂がありました。この船は、デメトリオス・パレレオスの時代にも、アテネの人々によって保存されていたもので、使い物にならなくなった木材は、徐々に新たな木材に置き換えられていました。これは発展する物ごとの論理的な疑問を論じる時の最適な例として、哲学者たちに取り上げられてきました。つまり、ある者は部品が変わっても、それは同じ船だと言い、別の者は同じ船ではないと主張したのです。
コードが、完全に予想通りに発展していかないことは分かっています。 “テセウスの船” の影響が 生じるのです。 しかし、時間をかけて持続的にコードベースを作っていく場合は、複合作用も発生します(これは、 1919年から 続くニューヨークの建設計画、 “2番街線” の影響と呼ぶのがふさわしいでしょう)。
まずはGitそのものを分析してみましょう。Gitは、早い段階で セルフホスティング となりました。これは、最も人気で古いGitプロジェクトです。
これは、コードが追加された年によって群分けされた、時間経過におけるコードラインの総数を示したグラフです。私はもっと大きな減衰が見られると思っていたので、2006年に書かれたコードが、いまだにコードベースに数多く残っているのが分かり驚きました。
個別のコミットに対する減衰を計算することもできます。全てのコミットをX=0に並べた場合、特定のRepo内のコードに対する、減衰の総数が分かります。この分析を実装するのは、いろいろな要素があるため、イメージするよりも多少難しくなります(新しいコミットほど時間の経過が少ないため、右端の曲線が少な目のコミットの総数を示す、ということが最も大きな理由です)。
Gitでは、このプロットは以下のようになります。
10年経っても、まだ40パーセントものコードラインが残っています。もっと幅広いオープンソース・プロジェクトを分析してみましょう(ある程度、ランダムに選んでいます)。
Gitは、他と少し異なるようです。指数関数的減衰でGitを描き、半減期を求めると、6年くらいまでになります。
う~ん。必ずしも完璧な一致とは言えませんが、「 全てのモデルが間違っているが、役に立つモデルもある 」という有名な言葉があります。私は、指数関数的減衰の説明力が好きです。コードには予想寿命があり、常に置き換えられるリスクがあるのです。
少し優れたモデルが 指数関数の総和 を描けるかは疑問です。これは、あるコードはすぐに変化し、別のコードはゆっくりと変化するようなRepoについては有効です。曲線の一致という本筋から離れたことを行う前に、「 パラメータが4つあれば象を描くことができ、パラメータが5つあれば象の鼻を揺らすことができる 」というフォン・ノイマンの言葉を自分自身に言い聞かせました。うまく一致させる方法はあるのでしょう。しかし、これは別の時に改めて考えようと思います。
いろいろなプロジェクトを全体的に見てみましょう(こちらも、ある程度ランダムに選んでいます)。
全体的な半減期は3.33年でした。覚えやすい数字でいいですね。しかし、プロジェクトによって、その差は 大きい ようです。集合モデルに非常に強力な予言力があるわけではなさそうです。任意のオープンソース・プロジェクトを示し、3.33年後に半分のコードが消えると予想することは難しいです。
さらなるRepo
Apache(別名 HTTPD はまた別の、昔からあるRepoです。
実に見事に 指数関数に一致していますね。
自分のRepoで実行してみたくありませんか? 繰り返しになりますが、コードは こちらで入手できます 。
ダントツのRepo
私のスクリプトを使えば、これらのRepoのほとんどは、どんなに長くても数分程度で分析できます。最後にテストしようと決めたのは、 Linux kernel です。これは 巨大 で、現時点でのコミット数は635,229です。これは、2番目に大きなRepoである前出の( Rails )の16倍の規模で、私のお粗末なコンピュータで分析するには数日を要しました。最終的に、処理速度を上げるために、少なくとも3週間コミットされているものと .c
ファイルだけに限定して、全ての git blame
を計算することにしました。
線が曲がりくねっていますが、これはサンプリングのメカニズムが原因だと思われます。それにしても、16Mもの膨大な行数が描き出すこの見事なグラフを見てください。各年のコード群の影響は、この規模にしては非常に滑らかです。この規模になると、個々のコミットはほとんど意味がありません。集積和はかなり正確に予想できます。 ニュートンの法則から熱力学への進展 のようなものです。
Linuxもまた、はっきりと線形成長の様相を呈しています。これは、その高いモジュール性と関係があると推測できます。 drivers
ディレクトリには、今までのところ、最多のファイル(22,091)があります。それに次ぐのが、様々なアーキテクチャに対するサポートを含む arch
(17,967)です。これは、見事に定義されたインターフェースを持っているので、複雑なスケールをうまく行いたい場合に最適だと言えます。
余談になりますが、複雑さを保ったまま、プロジェクトをうまくスケールするという概念が気に入っています。線形のスケーラビリティは究極の目標であり、その場合、ささいな機能のそれぞれが、だいたい同量のコードを使用しています。一方、粗悪なプロジェクトでは、プロジェクトのスケールは非常に線形的で、ささいな機能のどれもが大量のコードを使用しています。
話は戻りますが、LinuxとAngularなどの比較はなかなか興味深いものです。Angularは基本的に、Linuxとはまるで逆の様相を見せます。
Angularからランダムに抽出されたコード行の半減期は0.32年程度です。この事実は、Angularに悪影響を及ぼすのでしょうか。Angularのアーキテクチャは基本的に “線形” の、恒常的なものではないのでしょうか。Angularは新しいのだから、この比較は不公平だと思う方もいるかもしれません。これはもっともです。しかし、もし何らかの問題のある設計に対して悪影響を及ぼすとしても、驚かないでしょう。Angularをけなすつもりはありませんが、面白い対比だと言えます。
リポジトリによる半減期
任意のプロジェクトのサンプルと、それぞれの半減期を以下に示します。
project | half-life (years) | first commit |
angular | 0.32 | 2014 |
bluebird | 0.56 | 2013 |
kubernetes | 0.59 | 2014 |
keras | 0.69 | 2015 |
tensorflow | 1.08 | 2015 |
express | 1.23 | 2009 |
scikit-learn | 1.29 | 2011 |
luigi | 1.30 | 2012 |
backbone | 1.48 | 2010 |
ansible | 1.52 | 2012 |
react | 1.66 | 2013 |
node | 1.76 | 2009 |
underscore | 1.97 | 2009 |
requests | 2.10 | 2011 |
rails | 2.43 | 2004 |
django | 3.38 | 2005 |
theano | 3.71 | 2008 |
numpy | 4.15 | 2006 |
moment | 4.54 | 2015 |
scipy | 4.62 | 2007 |
tornado | 4.80 | 2009 |
redis | 5.20 | 2010 |
flask | 5.22 | 2010 |
httpd | 5.38 | 1999 |
git | 6.04 | 2005 |
chef | 6.18 | 2008 |
linux | 6.60 | 2005 |
注釈:プロジェクト 寿命 最初のコミット
moment の半減期がこれほど長いとは、興味深いことです。しかし、その理由は、コードの多くが文化圏固有だからです。このため、安定したコードの核を備えた状態で、時間をかけて線形的な追加が行われるという、線形のスケーラビリティを促進することになります。 express はもう一方の方向性において、極端な数値を示しています。expressが発表されて7年になりますが、そのコードは非常に早く変化しています。その理由として、(a)コードの線形スケーラビリティが欠けており、(b)おそらく、Node.jsの波に乗るべく、主流になろうとして人気をねらった、初めてのメジャーなJavascriptオープンソース・プロジェクトの1つである、といったことが考えられます。もしかすると、コードベースもイマイチなのかもしれませんが、よく分かりません。
コーディングは変化してきたのか?
プロジェクトが始まった年と半減期の間に、このような強い相関関係が見られる理由として、以下の3つのことが考えられます。
-
コードはプロジェクトの早期において、より激しく揺れ動く。そしてしばらくすると安定していく。
-
コーディングは2006年から2016年の間に変化した。さらに、最近のプロジェクトは昔より進化が速い。
-
スケーラブルで安定したプロジェクトだけが存続できるという、ある種の選択バイアスがある。
興味深いことに、1については、明らかなエビデンスのデータは何もありません。古いプロジェクトにおいて初期に書かれたコードの半減期は、後期に書かれたコードと同様に長いのです。3についても疑わしいものです。というのも、なぜ、プロジェクトの存続とコードの構造の間に関係があるのかは分からないからです(でも、たぶんあるのでしょう)。結論として言えるのは、 コードの書き方は、基本的に、過去10年間で変化した ということです。最近のプロジェクトでは、コードはより速いスピードで変化しているように思われます。
さて、この議論については Hacker News と Reddit に掲載されていますので、どうぞご覧ください。
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa