16

私は、コードを使用する約10の製品をサポートする大規模なプラットフォームプロジェクトに取り組んでいます。リモートファイルシステムアクセス
- - セキュリティ認証
- ベースのロジック(データベース
から構成データの検索 -
大規模プロジェクトでのDIのリファクタリング

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

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

私たちは既存のクラスでDIを使用することができると確信していますが、予想される時間は話す相手によって5〜70週間です。

DIを行う方法を説明する記事がたくさんありますが、DIを最も効率的にリファクタリングする方法を教えてくれるものは見つかりませんか? 30,000行のコードを通過してインターフェイスを拡張するためにCTRL + Rを押して、それを何回も建設業者に追加する必要がなく、これを行うツールはありますか? (私たちがそれを助けるならば、私たちは再シャーピングを持っています)もしそうでなければ、すぐにこれを達成するための理想的なワークフローは何ですか?

+1

リファクタリングは、手元の作業に必要なコードの小さなサブセットのみから開始することができます。そうすれば、あなたとあなたのチームはDIの感触を得て、DIの経験を集めるでしょう。 DIが動機づけをするデカップリングされたアーキテクチャは非常にテスト容易なので、単体テストを使用して何も破壊しないようにすることができます。 – lasseeskildsen

+1

この質問はおそらくhttp://programmers.stackexchange.comに適しています –

+2

これのためのツールはありません。それぞれのクラスをどのように分析してどの依存関係を抽出するかを考えてみましょう。ツールは信頼できる方法でこの分析を行うことはできません(あまりにも多くの情報が抽出されます)。ただし、抽出メソッドと抽出クラスのリファクタリングを手助けするツール(ResharperやCode Rushなど)がありますが、これはその時点では1つのクラスにしか適用されません。プロジェクト全体をワンクリックすることはありません。 – Steven

答えて

2

すべての返信いただきありがとうございます。私たちはほぼ一年後になりました。私は主に私自身の質問に答えることができます。

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

私たちは、これらの部品を単体テストを可能にするのではなく、望ましくない依存関係を引き起こさずに利用できるようにすることに重点を置いていました。これにより、問題へのアプローチ方法が異なります。この場合、実際の設計変更はありません。

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

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

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

  3. 多くのファイルで多くの小さなコードを変更します。この作業は、単一の開発者が行うのが最善です。変更を常にマージする必要がないようにするには、最大で2つです。

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

  5. 他の参照を取り除くために: a。上記のようにインタフェースを抽出します。 b。元の実装をコメントアウトします。ビルドエラーの一覧が表示されます。

  6. ReSharperのは、今大きな助けとなった:アップ/ダウン

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

主要なコードを変更せず、元の溶液中でこれらのクラスを利用するために、我々はブレットVeenstraは言及して、サービスロケータを介してそれらの依存関係を取得するオーバーロードコンストラクタを作成しました。これは反パターンかもしれませんが、このシナリオでは機能します。すべてのコードがDIをサポートするまでは削除されません。

約2-3週間(1.5人)で私たちのコードの約1/4をDIに変換しました。 さらに1年後、私たちはすべてのコードをDIに切り替える予定です。これは、単位テスト容易性に焦点を合わせるのとは異なる状況です。私は上記の一般的なステップはまだ機能すると思いますが、これにはSOCを実施するための実際の設計変更が必要です。

0

このコード変換を行うツールはないと思ってください。

ため - >

既存のコードベースにDIを使用すると、インターフェイス/抽象クラスの

  • 使用を含むであろう。この場合も、DI原則&のコード機能を念頭に置いて変換を容易にするために、右のキーオースを実行する必要があります。

  • 複数の/単一クラスの既存のクラスを効果的に分離して、コードモジュラー型または小型の再構成可能ユニットを維持します。

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をテストしていますか?他のテストでFakeFileWriterをテストしているので、FileRepositoryをテストしています。このクラス-FileRepositoryTestsは、入力パラメータのnullをテストする方が便利です。

偽物は何も巧妙ではありません - パラメータの検証もI/Oもありません。 FileRepositoryがコンテンツを任意の作業で保存できるようにするだけです。その目的は2つあります。ユニットテストを大幅にスピードアップし、システムの状態を破壊しないようにする。

このFileRepositoryも同様にファイルを読み込む必要がある場合は、IFileReaderを実装することもできます(極端なビットマップ)。最後に書き込まれたfilePath/xmlをメモリ内の文字列に格納して代わりに取得できます。


このように、どのようにこれにアプローチしますか?

多くのリファクタリングが必要な大規模なプロジェクトでは、単体テストをDI変更を行うクラスに組み込むことが常にベストです。理論的には、あなたのデータは数百の場所(コード内)にコミットされるべきではありませんが、いくつかの重要な場所に押し出されます。コード内でそれらを探し、それらのためのインターフェイスを追加します。非常に一般的な方法でデータソースから取得するために、あなたが設定されます

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); 
} 

:私が使用した1つのトリックは、各DBまたはこのようなインターフェイスの背後にあるインデックスのようなソースを非表示にすることです。 XmlRepositoryからDbRepositoryに切り替えるには、注入先を置き換えるだけです。これは、システムの内部に影響を与えることなく、あるデータソースから別のデータソースに移行するプロジェクトに非常に役立ちます。オブジェクトを使用するようにXML操作を簡単に変更することはできますが、この方法では新しい機能を維持し実装する方がはるかに簡単です。

私が与えることができる唯一の他のアドバイスは、一度に1つのデータソースです。一度に多くのことをする誘惑に抵抗する。実際にファイル、DB、Webサービスを一度に保存しなければならない場合は、Extract Interfaceを使用し、呼び出しを偽装して何も返しません。一度にたくさんのことをするのは本当のジャグリングですが、最初の原則から始めるよりも簡単に戻すことができます。

幸運を祈る!

+0

あなたのアプローチは合理的だと思います。しかし、FakeFileWriterを作成するのではなく、IFileWriterのスタブを作成するためにいくつかの模擬ライブラリを使用する方がよいでしょうか? –

+0

うん。あなたはdudの実装のための単体テストを書いていません。 'CreateInstance'メソッドを再度使用してモックオブジェクトを設定し、作成シナリオと必要なシナリオを一元化します。 –

+0

"通常の型を使用する既定のコンストラクタを用意してください"できるだけこれを避けるのが最善だと思います。問題は、あなたのクラスがまだインターフェイスの具体的な実装に固定されていることです。理想的には、インターフェイスに依存したいだけです。デフォルトのコンストラクタは、具体的な依存関係を強制します。 – Robin

3
私はあなたがなど、その場合には

のStructureMap、Funq、Ninject、のようなIoCのツールを使用するために探していると仮定してい

は、リファクタリングの仕事は本当にあなたのエントリポイントを更新(またはComposition Roots)で始まりますコードベースでこれは、特にスタティックスの普及とオブジェクトのライフタイムの管理(キャッシング、遅延ロードなど)に大きな影響を与える可能性があります。 IoCツールを配置してオブジェクトグラフを配線すると、DIの使い方を広げて利益を享受することができます。

まず、設定のような依存関係(単純な値オブジェクトである必要があります)に焦点を当て、IoCツールで解決呼び出しを開始します。次に、ファクトリクラスを作成し、それらを注入してオブジェクトのライフタイムを管理します。あなたのオブジェクトの大部分がDIを使用している紋章に到達し、SRPが完全にSRPに達するまで、あなたは後方に(そしてゆっくりと)進んでいるように感じられます。そこから降りるべきです。懸念がより分かれば、コードベースの柔軟性と変更のスピードが飛躍的に向上します。

注意:あなた自身が万能薬である「サービス・ロケーター」を振りかけることに、あなた自身が気づかれないようにしてください。実際はDI antipatternです。私はあなたがは最初ににこれを使用する必要があると思うが、コンストラクタまたはセッター注入のDI作業を完了し、サービスロケータを削除する必要があります。

0

この本は、おそらく非常に参考になる:

は、レガシーコードと効果的に作業する - マイケル・C.羽 - http://www.amazon.com/gp/product/0131177052

私は小さな変化で始まるお勧めします。コンストラクタを通して注入される依存関係を徐々に移動する。常にシステムを動作させておきます。コンストラクタから挿入された依存関係からインタフェースを抽出し、ユニットテストでラッピングを開始します。それが理にかなったら道具を持ってきてください。依存関係注入とフレームワークのモックをすぐに使用する必要はありません。コンストラクタを介して手動で依存関係を注入することで、多くの改善を行うことができます。

1

あなたはツールについて尋ねました。このような大きなリファクタリングで役立つツールの1つはnDependです。私はそれを使用して、リファクタリングの取り組みを対象とする場所を特定しました。

私はこのプロジェクトを行うためにnDependのようなツールが必要であるという印象を与えたくないので、私はそれを言いたいと思います。ただし、コードベースの依存関係を視覚化すると便利です。 14日間の完全機能試験が付属しており、お客様のニーズに十分対応できます。

0

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

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

まず、コンストラクタへのダイレクトコールの一部をサービスロケータ解決呼び出しに置き換えます。これにより、最初に必要となるリファクタリングの量が少なくなり、DIの利点が得られます。

あなたの電話がコンポジションルートに近づくと、サービスロケータの使用を取り消すことができます。

関連する問題