モジュール分割

ありがちな技術的負債の1つに、単一のライブラリに債務を詰め込み過ぎるというのがあります。

取り急ぎ開発を進めていると、クラスの置き場に困ってついつい1つのライブラリになんでもかんでも置いてしまうということがあります。 そして、後から振り返ると、別々のライブラリに分けたくなったりします。

例えば、以下の様な状態です。

詰め込み過ぎたライブラリ

「よく使う処理を拡張メソッドにして、ライブラリ化しよう」と試みて、気がつけば、文字列関連、ネットワーク関連、数値処理関連など、全然違う債務を1箇所に詰め込み過ぎてしまっています。 こういう状態を「一枚岩」(monolithic)であると言います。

そして、このライブラリの利用者が増えてきた頃に、「文字列関連だけが使いたい」「ネットワーク関連だけが使いたい」など、個別の要求が出てきます。

反省して複数のライブラリに分割することになったとして、問題は既存のユーザーです。 「アセンブリ+名前」で型を探すわけで、別ライブラリに移動してしまうと互換性を崩します。

そこで、型フォワーディングの出番です。 詰め込み過ぎたライブラリを別のライブラリに分割した上で、元のライブラリにはTypeForwardedTo属性だけを書きます。

詰め込み過ぎたライブラリをモジュール分割

これで、互換性は崩さずに型を移動できます。

修正後のように、債務ごとに綺麗に分かれた状態を「モジュール型」(modular)と言います。 一枚岩な状態は、依存関係が大きくなりすぎたり、部分的な更新ができなかったりといった問題を抱えることになるので、 モジュール型な状態を保つよう心がけるべきです。

余談: 逆のやり方

ちなみに、TypeForwardedTo属性を付けるのを逆にすることもできます。

上記の例で言うと、

  • 将来的にライブラリを StringClassLibrary, HttpClassLibrary, NumericClassLibrary の3つに分けることに決まった
  • が、今は分けてる余裕ない。MonolithicClassLibraryは今のままで維持したい
  • なので、新しく作ったStringClassLibrary, HttpClassLibrary, NumericClassLibrary の側にTypeForwardedTo属性をつけて、MonolithicClassLibraryに型を転送する

というやり方もできます。

内部的には一枚岩のままなので、根本的には問題解決しません(依存関係は大きいままだし、部分更新できない)が、 「将来こう分割するよ」という予告にはなります。

余談: .NET 標準ライブラリのモジュール化

「初期段階で一枚岩に作ってしまって、後からモジュール分割」という流れ、 .NET Frameworkの標準ライブラリが典型例だったりします。

.NET Framework 4までは、標準ライブラリ中のクラスの大半がmscorlib.dllというアセンブリに詰め込まれていました。 それが、.NET Framework 4.5で、System.Net.dll, System.Threading.dll, System.Linq.dll… など、債務ごとに分割されました。

ちなみに、方法としては前節の「逆のやり方」でやっています。 mscorlib.dll自体は元のままで、モジュール分割した側のアセンブリにTypeForwardedTo属性が入っています。

一方で、2015年に、Windowsデスクトップ向けの.NET Frameworkとは別に、 クロスプラットフォーム向けの「.NET Core」というバージョンの別実装が出てきました。

こっちのバージョンでは、最初からモジュール分割済みの実装が行われています。

更新履歴

ブログ