web-dev-qa-db-ja.com

既存のレガシーコードでグローバル変数を処理するにはどうすればよいですか(または、グローバルヘルまたはパターンヘル)。

ですから...何百ものグローバル変数を持つこのかなり複雑なプロジェクトがあります(LOCが10k以下ですが、コードが重複しているのでわかりません)。プロジェクトは他のプロジェクトへの依存度が高く、他の多くのプロジェクトもそれに依存しています。私はほとんど、このプロジェクトの一部、つまりモジュールの「囲まれたセクション」だけをリファクタリングする責任を自分で引き継いでいます。元の開発者はいません。

私はクラスでルーチンを構造化する方法を考案しました(分割 megamoths 、いくつかのクラス階層を形成し、あちこちで少しの戦略を作ります、あまりにも空想的ではありません)、目的はそれをより簡単にすることです他の開発者が機能を追加し、適切な単体テストを追加できるようにするため)。

私の新しいクラスは、更新されているデータの現在グローバルな配列に適用される計算を提供し、計算自体が状態(合計、最後に処理されたベクトルの最後の値など)を維持する必要があります。あなたはそれらをファンクターとして見ることができます。

ただし、グローバルに対処する方法はわかりません。他のモジュールへの依存関係のため、すべてのグローバルを非グローバルに変更することはできないと思いますが、今はリファクタリングしません。また、新しいクラスの多くはデータを共有する必要があります。だから、できる...

  • 新しいクラス間で共有されないグローバルについては、そのままにしておきます。 Registry を使用するか、少なくとも#defineやその他の変数を使用してスコープを制限し、使用される場所にコンテキストを提供することを考えていました。
  • 新しいクラス間でが共有されるグローバルの場合、参照を使用して基本クラスを作成するか、シングルトンを使用してそれらを渡すか、または両方の組み合わせを使用します。

これらのパターンを使用することで、害になるかどうかはわかりません。

だから、私の質問は:何が良いのか、既存のグローバル変数と一緒に生きるか、シングルトンやレジストリのようなパターンで積極的にそれらを調整しますか?これらのスキームへの提案、またはより良いスキームがありますか?

3
ArthurChamz

グローバルから非グローバルにゆっくりと比較的安全に移動する1つの方法は、できる限りグローバルを半合理的なクラスにグループ化することです。置き換えるグローバルに対応するメンバーを使用して、新しいクラスのグローバルインスタンスを宣言します。グローバルを削除します。膨大な数のコンパイラエラーを回避し、グローバルへの各参照をグローバルインスタンスの対応するメンバーへの参照に変更します。

これで、クラスに新しいクラスのグローバルインスタンスを直接認識させることから徐々に切り替えることができるようになりました。代わりに、グローバルインスタンスをクラスに渡して、キャンを開始することができます。やがて、すべてのクラスからグローバルインスタンスの直接的な知識をすべて削除し、最終的にグローバルインスタンスを削除して、事実上ゆっくりとレジストリまたは依存関係注入(クラスの1つからグローバルインスタンスの直接の知識を削除するたびに何をしたかに応じて)。

クラスの1つがグローバルインスタンスを見つける方法の直接の知識を失うたびに、グローバルインスタンスがコードベースに存在していても、変更やテストが可能になることに注意してください。したがって、すべての作業を事前に行わなくても、いくつかの利点が得られます。グローバルが広く参照されている場合、大きなコードベースでは時間がかかりすぎる可能性があります。

もちろん、最初の段落ですべてを完了する必要があります。大規模なコードベースを使用すると、コンパイラーと一緒に多くの時間を過ごすことができます。

15
psr

どのくらい遠くまで行くかは、グローバルの性質に大きく依存します。それらがほとんど読み取られてほとんど変更されない場合(場所を見つけるのが簡単な数か所のみ)、抜本的な変更を加えることをためらい、変更に関するフレームワークを提供し、ドキュメント、プログラマーの規律、およびコードレビューに依存して、フレームワークが使用されていることを確認します。

ただし、グローバルが多くの場所から、または常に(または両方)変更されている場合は、リファクタリングをより積極的に行う必要があります。

シングルトン-「グローバルはすべての悪の道である」というドグマを回避するためのグローバルな架空の名前、およびJavaなどの言語でグローバルの概念がありません。これらはグローバルではあまり役立ちません。この場合、使用パターンが自然にシングルトンになる傾向がある場合を除きます(行間の読み取りから、これは間違いです)。

レジストリ-より良いオプションですが、アイテムが本当にグローバルである必要がある場合を除き、理想的ではありません。定期的に書き込まれる場合は理想的とは言えません。

これらのグローバルを注意深く確認し、最初に、それらをグローバルにする必要があるかどうかを確認します。明確な理由がない場合、それを修正しようとする試みは、原因ではなく症状を扱っています。これは、かなりグローバルなものです。修正する(グローバルにしないなど)オプションがないと判断した場合、正当な理由は「努力する価値がない」、「予期しない方法で他のコードを壊す可能性がある」などであり、その後に固定石膏を貼る良いアイデアかもしれません。

5K SLOCは大きなプロジェクトではありません。この状態で500万SLOCを処理してみてください。 5K SLOCのみで、優れたツールサポートがあれば、大規模なリファクタリングは大きな労力ではありません。

7
mattnz

[廃止]属性(またはc#を使用していない場合は何でも)ですべてのグローバルにフラグを設定します

次のように、Dependency Injectionのインフラストラクチャ配線をセットアップします。

using (var mainForm = factory.Get<MainForm>()) {
      Application.Run(mainForm);
}

次に、バックログから機能を実装するビジネスに取り掛かります。既存のコードを変更すると、IDEは不良アクター(廃止された変数)を指摘します。パスが交差する場合は、代わりに依存関係注入コンテナーから取得してみてください。必要はありません。意図しない放射性降下物が存在することが保証されている大きな大きな変更ですべてのインスタンスを置き換えます。

進むにつれて、これらの邪悪な行為者の1つまたは2つのユースケースにますます自信を持つようになります。同時に、選択したDIコンテナーを使用してスキルを構築します。

次に、ある日、DIコンテナーがほとんどのコードベースに接続されたときに、小さめのクラスを取得し、その中にあるすべての[廃止された]変数を打ち消します。これを行うには、DIコンテナーを使用してインスタンス化する、最も単純なテストでテストフィクスチャをスタブします。

    var factory = new NinjectFactory();
    var mainForm = factory.Get<MainForm>();
    Assert.That(mainForm, Is.Not.Null, "MainForm"); 

次に、グローバル変数を見つけて、コンストラクターに渡します。グローバル変数のクラスのDIを構成していない場合、テストは失敗します。もちろん、そのクラスには、展開する必要がある依存関係がありますが、テスト(およびできればApp)を実行するために必要なだけ、展開を試行してください。

ところで-私は今、同じような状況で首の奥深くにいます。 (ただし、はるかに大きなプロジェクトですが、メインフォームだけでも3k行のコードでした!)慣れてきたら、それで十分満足できます。

1
DanielEli

世界中のグローバルに関する重大な問題の1つは、プログラムの現在のコンテキストに応じて、グローバル変数が再利用されてさまざまな「サブシステム」を制御することです。だからあなたは何ができますか:

  1. グローバル変数のグローバルレジストリクラスを追加します。
  2. ソフトウェアをテストして(たとえば、デバッガーまたは単体テストで)、グローバル変数の信頼性が実際に1つのドメインのみにあるかどうかを確認します。
  3. グローバル変数の責任を分割することに意味があるかどうかを確認してください。

また、これを単体テストレベルまたは統合テストレベルでリファクタリングする場合は、自動テストを構築する必要があります。

キム

1
KimKulling