15

私は、コードを使用する約 10 の製品をサポートする大規模なプラットフォーム プロジェクトに取り組んでいます。

これまでのところ、すべての製品が当社のプラットフォームの機能をすべて使用し て い ます



新製品については、プラットフォームがもたらすインフラストラクチャなしで、機能のより小さなサブセットをサポートするように求められました。私たちのアーキテクチャは古いですが (2005 年くらいからコーディングが開始されています)、かなりしっかりしています。

既存のクラスで DI を使用してそれを行うことができると確信していますが、そうするのにかかる推定時間は、誰と話すかによって 5 週間から 70 週間の範囲になります。

DI の実行方法を説明する記事はたくさんありますが、最も効率的な方法で DI をリファクタリングする方法を説明する記事は見つかりませんでした。30.000 行のコードを実行し、インターフェイスを抽出してコンストラクターに追加するために CTRL+R を何度も押す必要がなく、これを行うツールはありますか? (それが役立つ場合は resharper があります) そうでない場合、これを迅速に達成するための理想的なワークフローは何だと思いますか?

4

7 に答える 7

3

StructureMap、Funq、Ninject などの IoC ツールの使用を検討していると仮定しています。

その場合、リファクタリングの作業は、コードベースのエントリ ポイント (またはコンポジション ルート) を更新することから始まります。これは、特に静的を広範囲に使用し、オブジェクトの有効期間を管理している場合 (キャッシュ、遅延ロードなど)、大きな影響を与える可能性があります。IoC ツールが配置され、オブジェクト グラフが接続されると、DI の使用を広げてメリットを享受できるようになります。

最初に設定のような依存関係 (単純な値オブジェクトである必要があります) に注目し、IoC ツールを使用して解決の呼び出しを開始します。次に、Factory クラスを作成し、それらを注入してオブジェクトの有効期間を管理します。ほとんどのオブジェクトが DI を使用し、当然のことながら SRP を使用している頂上に到達するまでは、後退している (そしてゆっくりと) 感じます。そこからは下り坂になるはずです。関心事の分離が改善されると、コードベースの柔軟性と変更を加える速度が劇的に向上します。

注意点: 「Service Locator」をあらゆる場所に散りばめれば万能薬だと思い込まないでください。実際にはDIのアンチパターンです。最初はこれを使用する必要があると思いますが、コンストラクターまたはセッターのインジェクションで DI 作業を終了し、サービス ロケーターを削除する必要があります。

于 2013-05-22T19:50:06.650 に答える
3

すべての返信に感謝します。ほぼ 1 年が経ちましたが、私は自分自身の質問にほぼ答えることができると思います。

もちろん、lasseeskildsen が指摘するように、新製品で再利用されるプラットフォームの部分のみを変換しました。これはコード ベースの部分的な変換にすぎないため、依存性注入に対する DIY アプローチを採用しました。

私たちの焦点は、不要な依存関係を持ち込むことなくこれらのパーツを利用できるようにすることであり、それらの単体テストを可能にすることではありませんでした。これにより、問題へのアプローチ方法が異なります。この場合、実際の設計変更はありません。

関連する作業はありふれたものであるため、迅速に、または自動的に行う方法が問題になります。答えは、自動化できないということですが、いくつかのキーボード ショートカットと再シャープ化を使用すると、非常に高速に実行できます。私にとって、これは最適なフローです:

  1. 私たちは複数のソリューションに取り組んでいます。すべてのソリューション ファイルにすべてのプロジェクトを含む一時的な「マスター」ソリューションを作成しました。リファクタリング ツールは、バイナリ参照とプロジェクト参照の違いを検出できるほど常にスマートであるとは限りませんが、これにより、少なくとも複数のソリューションで部分的に機能するようになります。

  2. カットする必要があるすべての依存関係のリストを作成します。機能ごとにグループ化します。ほとんどの場合、関連する複数の依存関係に一度に取り組むことができました。

  3. 多くのファイルにわたって多くの小さなコード変更を行うことになります。このタスクは、常に変更をマージする必要がないように、1 人の開発者、またはせいぜい 2 人の開発者が行うのが最適です。

  4. 最初にシングルトンを取り除く: このパターンから変換した後、インターフェースを抽出します (リシャーパー -> リファクタリング -> インターフェースを抽出) シングルトン アクセサーを削除して、ビルド エラーのリストを取得します。ステップ 6 に進みます。

  5. 他の参照を取り除くには: 上記のようにインターフェイスを抽出します。b. 元の実装をコメントアウトします。これにより、ビルド エラーのリストが取得されます。

  6. Resharper は大きな助けになります。

    • Alt + shift + pg down/up で、壊れた参照をすばやく移動できます。
    • 複数の参照が共通の基本クラスを共有している場合は、そのコンストラクターに移動し、ctrl + r + s (「メソッド シグネチャの変更」) を押して、新しいインターフェイスをコンストラクターに追加します。Resharper 8 には、「コール ツリーで解決する」オプションが用意されています。つまり、継承クラスの署名が自動的に変更されるようにすることができます。これは非常に優れた機能です (バージョン 8 の新機能のようです)。
    • コンストラクタ本体で、注入されたインターフェイスを存在しないプロパティに割り当てます。Alt + Enter を押して「プロパティの作成」を選択し、必要な場所に移動すれば完了です。5b からコードのコメントを外します。
  7. テスト!すすいで繰り返します。

Brett Veenstra が言及しているように、大幅なコード変更を行わずに元のソリューションでこれらのクラスを利用するために、サービス ロケーターを介して依存関係を取得するオーバーロードされたコンストラクターを作成しました。これはアンチパターンかもしれませんが、このシナリオでは機能します。すべてのコードが DI をサポートするまで削除されません。

約 2 ~ 3 週間 (1.5 人) で、コードの約 4 分の 1 をこのように DI に変換しました。さらに 1 年が経過し、すべてのコードを DI に切り替えています。これは、焦点が単体テスト容易性に移行するため、状況が異なります。上記の一般的な手順は引き続き機能すると思いますが、SOC を適用するには実際の設計変更が必要です。

于 2013-12-06T04:47:32.063 に答える
0

私が変換にアプローチする方法は、状態を永続的に変更するシステムのあらゆる部分を調べることです。ファイル、データベース、外部コンテンツ。一度変更して再読すると、完全に変更されましたか? これは、それを変更する最初の場所です。

したがって、最初に行うことは、次のようにソースを変更する場所を見つけることです。

class MyXmlFileWriter
{
   public bool WriteData(string fileName, string xmlText)
   {   
      // TODO: Sort out exception handling
      try 
      {
         File.WriteAllText(fileName, xmlText);  
         return true; 
      } 
      catch(Exception ex) 
      { 
         return false; 
      }
   }
}

次に、単体テストを作成して、リファクタリング中にコードが壊れていないことを確認します。

[TestClass]
class MyXmlWriterTests
{
   [TestMethod]
   public void WriteData_WithValidFileAndContent_ExpectTrue()
   {
      var target = new MyXmlFileWriter();
      var filePath = Path.GetTempFile();

      target.WriteData(filePath, "<Xml/>");

      Assert.IsTrue(File.Exists(filePath));
   }

   // TODO: Check other cases
}

次に、元のクラスからインターフェイスを抽出します。

interface IFileWriter
{
   bool WriteData(string location, string content);
}

class MyXmlFileWriter : IFileWriter 
{ 
   /* As before */ 
}

テストを再実行して、すべてがうまくいくことを願っています。古い実装が機能することを確認しているため、元のテストを保持してください。

次に、何もしない偽の実装を書きます。ここでは、非常に基本的な動作のみを実装したいと考えています。

// Put this class in the test suite, not the main project
class FakeFileWriter : IFileWriter
{
   internal bool WriteDataCalled { get; private set; }

   public bool WriteData(string file, string content)
   {
       this.WriteDataCalled = true;
       return true;
   }
}

次に、単体テストを行います...

class FakeFileWriterTests
{
   private IFileWriter writer;

   [TestInitialize()]
   public void Initialize()
   {
      writer = new FakeFileWriter();
   }

   [TestMethod]
   public void WriteData_WhenCalled_ExpectSuccess()
   {
      writer.WriteData(null,null);
      Assert.IsTrue(writer.WriteDataCalled);
   }
}

単体テストされ、リファクタリングされたバージョンがまだ機能しているので、注入されたときに、呼び出し元のクラスが具体的なバージョンではなくインターフェイスを使用していることを確認する必要があります!

// Before
class FileRepository
{
   public FileRepository() { }

   public void Save( string content, string xml )
   {
      var writer = new MyXmlFileWriter();
      writer.WriteData(content,xml);
   }
}

// After
class FileRepository
{
   private IFileWriter writer = null;

   public FileRepository() : this( new MyXmlFileWriter() ){ }
   public FileRepository(IFileWriter writer) 
   {
      this.writer = writer;
   }

   public void Save( string path, string xml)
   {
      this.writer.WriteData(path, xml);
   }
}

それで、私たちは何をしましたか?

  • 通常の型を使用するデフォルトのコンストラクターを持つ
  • IFileWriter型を取るコンストラクタを持つ
  • 参照されたオブジェクトを保持するためにインスタンス フィールドを使用しました。

次に、の単体テストを作成FileRepositoryし、メソッドが呼び出されることを確認します。

[TestClass]
class FileRepositoryTests
{
   private FileRepository repository = null;

   [TestInitialize()]
   public void Initialize()
   {
    this.repository = new FileRepository( new FakeFileWriter() );
   }

   [TestMethod]
   public void WriteData_WhenCalled_ExpectSuccess()
   {
       // Arrange
       var target = repository;

       // Act
       var actual = repository.Save(null,null);

       // Assert
       Assert.IsTrue(actual);
   }
}

わかりましたが、ここで本当にテストしているFileRepositoryのは かFakeFileWriter? FileRepository他のテストが を個別にテストしているように、 をテストしていますFakeFileWriter。このクラス -FileRepositoryTests入ってくるパラメータの null をテストするのにより便利です。

偽物は巧妙なことは何もしていません - パラメータの検証も I/O もありません。FileRepository がコンテンツを保存できるように、ただ座っているだけです。その目的は 2 つあります。単体テストを大幅にスピードアップし、システムの状態を乱さないようにする。

この FileRepository がファイルも読み取る必要がある場合は、IFileReader も実装するか (これは少し極端です)、最後に書き込まれた filePath/xml をメモリ内の文字列に保存し、代わりにそれを取得することができます。


基本的なことは終わりにします - どのようにこれに取り組みますか?

多くのリファクタリングが必要な大規模なプロジェクトでは、DI の変更を受けるクラスに単体テストを組み込むことが常に最善です。理論的には、データを [コード内の] 何百もの場所にコミットするのではなく、いくつかの重要な場所にプッシュする必要があります。コード内でそれらを見つけて、それらのインターフェイスを追加します。私が使用した 1 つのトリックは、次のようなインターフェイスの背後にある各 DB またはインデックスのようなソースを非表示にすることです。

interface IReadOnlyRepository<TKey, TValue>
{
   TValue Retrieve(TKey key);
}

interface IRepository<TKey, TValue> : IReadOnlyRepository<TKey, TValue>
{
   void Create(TKey key, TValue value);
   void Update(TKey key, TValue);
   void Delete(TKey key);
}

これにより、非常に一般的な方法でデータ ソースから取得できるようになります。注入する場所を入れ替えるだけで、XmlRepositoryから に切り替えることができます。DbRepositoryこれは、システムの内部に影響を与えることなく、あるデータ ソースから別のデータ ソースに移行するプロジェクトに非常に役立ちます。XML 操作をオブジェクトを使用するように変更するのは簡単ですが、この方法を使用すると、新しい機能の保守と実装がはるかに簡単になります。

私ができる他の唯一のアドバイスは、一度に 1 つのデータ ソースを実行し、それを実行し続けることです。一度に多くのことをやりすぎないようにしましょう。本当にファイル、DB、および Web サービスに一度に保存する必要がある場合は、Extract Interface を使用して呼び出しを偽造し、何も返さないでください。一度にたくさんのことをするのは本当にジャグリング行為ですが、最初の原則から始めるよりも簡単にそれらをスロットに戻すことができます.

幸運を!

于 2013-05-08T12:23:33.703 に答える
0

このコード変換を行うためのツールがあるとは思わないでください。

なぜなら - >

既存のコード ベースで DI を使用するには、

  • インターフェイス/抽象クラスの使用。ここでも、DI の原則とコード機能を念頭に置いて変換を容易にするために、適切な選択を行う必要があります。

  • コードをモジュラーまたは小さな再利用可能なユニットに保つために、既存のクラスを複数/単一のクラスに効果的に分離/統合します。

于 2013-01-16T10:34:54.453 に答える
0

あなたが説明したことは、プロセスの大部分です。各クラスを通過し、インターフェイスを作成して登録します。コンポジション ルートへのリファクタリングにすぐにコミットする場合、これは最も問題になります。MVC の場合は、コントローラーに注入することを想定することを意味します。

これは大変な作業になる可能性があり、コードが多くのオブジェクトを直接作成する場合、すべてを一度に行うのは非常に複雑になる可能性があります。このような場合、Service Locator パターンを使用して手動で解決を呼び出すことは許容できると思います。

コンストラクターへの直接呼び出しの一部をサービス ロケーターの解決呼び出しに置き換えることから始めます。これにより、最初に必要なリファクタリングの量が減り、DI のメリットが得られるようになります。

時間が経つにつれて、呼び出しはコンポジション ルートにどんどん近づき、サービス ロケーターの使用を削除し始めることができます。

于 2013-06-10T14:35:59.740 に答える