読者です 読者をやめる 読者になる 読者になる

Sphinx コードの半減期と未来予想図

Twitter で流れてきた コードの半減期とテセウスの船 | 開発手法・プロジェクト管理 | POSTD を読んで、興味深かったのでさっそく Sphinx のコードでも実行してみました。

その結果がこちらです。
f:id:tk0miya:20161228225526p:plain

コードの増え方は時期によって波がありますが、やはり Sphinx もコードの量は右肩上がりに増えています。そして、古いコードは徐々に整理されて少なくなっています。

面白いのは、生き延びた古いコードはある程度安定しているためか、削除されることが少なくなってきている一方で、新しく追加されたコードは入れ替わりが激しい傾向が読み取れます。
Sphinx はまだコードの追加と削除が繰り返される、活発なプロジェクトだということがわかります。

モジュールごとの半減期

さて、せっかくの機会ですから、別の角度からも見てみましょう。
f:id:tk0miya:20161228225810p:plain
このグラフは Sphinx の主要モジュールの年代ごとのコード比率です。
以下のコマンドでえいやっと計算しました。

$ find sphinx/environment -type f -exec git annotate {} \; | perl -ne 'm/(\d+)-\d+-\d+/; print $1, "\n"' | sort | uniq -c
 155 2007
 180 2008
 128 2009
  81 2010
  80 2011
   6 2012
  10 2013
 272 2014
  42 2015
1159 2016

directive, builders, writers, util, ext, application などの主要なモジュール群は 40〜50% 程度の実装が 2010年までに実装されたものだということがわかります。
一方、domains や environment は 25% 程度とコードが入れ替わっていることがわかります。

また、environment を除くモジュールは約 25% 前後が 2016年にコミットされたものだということがわかります。
ここ 1年であちこちに手を入れていったので、その成果がここに現れているということですね。

さて、2016年に変更されたものでも、ひときわ目立つものがあります。見ておわかりの通り、 environment です。
なんと environment モジュールは 50% 超が 2016年製のコードです。

大きく書き換わった environment モジュール?

いったいこの 2016年、 environment モジュールに何が起きたのでしょう?

種明かしをすると、実はコードの内容は大きく変わっていません。
2016年10月、 https://github.com/sphinx-doc/sphinx/pull/2945/files という PR が取り込まれました。
これは、environment をリファクタリングし、いくつかの細かいモジュールに分割する変更です。
モジュール化にあたり、インターフェースを定義するなどの多少の調整は行っていますが、これまでのコードからは大きく変化はありません。
つまり、この 50% という大きな変更はファイルの分割によって発生しているものでした。
実は git blame が理解していないだけで、そんなに大きい変更ではなかったんですね。

生まれ変わる Sphinx

それでは、どうして Sphinx プロジェクトではこのような変更を行ったのでしょうか?
その答えは Sphinx のコードの複雑さにあります。

Sphinx は数多くの人に使われていて、多くの API によって拡張可能となっているにもかかわらず、その内部は結合度が高く、モジュールの粒度もかなり大きいままです。

現在、このおおきな Sphinx のコアモジュールを整理する動きがあります*1
特に environment モジュールはコードベースが巨大で、持っている機能や責任範囲も広く複雑です。
担当する責務をいくつか例に上げると、

  • ドキュメントソースの読み込み
  • ドキュメントの中間データの管理、保存
  • その他の中間データ(インデックスやドメインデータなど)の管理、保存
  • 目次データの生成
  • 更新されたドキュメントソースの検知
  • アセットファイル (ダウンロードファイルや画像ファイルなど)の管理
  • ログ管理
  • パス変換
  • ビルド中のコンテキスト情報の保持
  • 参照(リンク)の解決
  • ドメイン機能の提供
  • インデックスの管理、解決
  • etc. etc.

などがあります。
あきらかに単一責任の原則に反していますし、メンテナンスをしている側からも非常にわかりづらい、魔窟化したモジュールのひとつでした。

というわけで、あの PR ではこの魔窟を切り広げるための第一歩として投げ込まれたものです。
ただ、これはあくまで一歩目でしかありません。

まだまだ続く Sphinx のリニューアル

今後も Sphinx の構造をシンプルにしていく活動は続きます。
(個人的な) ゴールとして、次のようなことを目標にしています。

  • Sphinx を細かくモジュール分割すること
  • モジュールの結合度を下げること
  • Sphinx 自身を Sphinx API で作ること

Sphinx はさまざまな API を提供する大きなフレームワークの一種です。
Sphinx 自体もそのフレームワークを利用している (HTML 出力や LaTeX 出力などは拡張として実現している) のですが、一方でフレームワーク部分はあまりきれいに整理されておらず、どちらかというとモノリシックな構造でできています。

今後はモジュール化をすすめ、フレームワーク部分をより整理したものを目指していきます。
おそらくその過程で、Sphinx コアはスリムで拡張しやすくなり、整理された API セットがみつかっていくでしょう。
今のところ、このスリムな Sphinx & API セット を 2.0 とすべく、こつこつリファクタリングを繰り返しています。

次は…?

いまはログまわりの改修を進めています。
https://github.com/sphinx-doc/sphinx/pull/3267

Sphinx はメッセージ出力、ログ出力を application, environment という 2大モジュールで経由するようにデザインされています。
そのため、ログを出力するにはどちらかのインスタンスを持っている必要があるため、単にログを出力したいがために application オブジェクトが渡されているなど、無駄に結合度が高い作りになっていました。

この PR では、これらを切り離し、python の logging パッケージで置き換えようとしています。

いつの日か Sphinx API 2.0 がリリースされたら、焼肉おごってください。

*1:なんとなく他人事っぽく書いてみたものの、動いているのは自分なので「やってます」が正しいですね