単一の構成オブジェクトは悪い考えですか?


43

ほとんどのアプリケーションでは、シングルトンまたは静的な「構成」オブジェクトを使用して、ディスクからさまざまな設定を読み取ります。ほぼすべてのクラスがさまざまな目的で使用します。基本的には、名前/値のペアの単なるハッシュテーブルです。それは読み取り専用なので、私はそんなにグローバルな状態を持っているという事実にあまり関心がありません。しかし、ユニットテストを始めた今、問題になり始めています。

1つの問題は、通常、実行するのと同じ構成でテストしたくないということです。これにはいくつかの解決策があります。

  • configオブジェクトに、テストにのみ使用されるセッターを与えると、異なる設定を渡すことができます。
  • 単一の構成オブジェクトの使用を続けますが、シングルトンから、必要なすべての場所に渡すインスタンスに変更します。その後、アプリケーションで1回、テストで1回、さまざまな設定で構築できます。

しかし、どちらにしても、2番目の問題が残っています。ほとんどすべてのクラスがconfigオブジェクトを使用できます。そのため、テストでは、テストするクラスの構成を設定する必要がありますが、その依存関係もすべて設定する必要があります。これにより、テストコードが見苦しくなります。

この種の設定オブジェクトは悪い考えだという結論に達し始めています。どう思いますか?いくつかの選択肢は何ですか?そして、どこでも設定を使用するアプリケーションのリファクタリングをどのように開始しますか?


構成は、ディスク上のファイルを読み取ることで設定を取得しますか?それでは、「test.config」ファイルだけを読み取れないのはなぜですか?
アノン。

これは最初の問題を解決しますが、2番目の問題は解決しません。
JW01

@ JW01:それでは、すべてのテストに著しく異なる構成が必要ですか?テスト中にどこかにその構成をセットアップする必要がありますよね?
アノン。

本当です。ただし、単一の設定プール(テスト用の別のプール)を引き続き使用すると、すべてのテストで同じ設定が使用されます。また、異なる設定で動作をテストしたい場合があるため、これは理想的ではありません。
JW01

「したがって、テストでは、テスト対象のクラスの構成を設定する必要がありますが、そのすべての依存関係も設定する必要があります。これにより、テストコードが見苦しくなります。」例がありますか?私は、構成クラスに接続するアプリケーション全体の構造ではなく、構成クラス自体の構造が「thingsい」ことをしていると感じています。クラスの構成をセットアップして、その依存関係を自動的に構成するべきではありませんか?
-AndrewKS

回答:


35

単一の構成オブジェクトに問題はなく、すべての設定を1か所に保持する利点がわかります。

ただし、その単一のオブジェクトをどこでも使用すると、構成オブジェクトとそれを使用するクラスとの間の結合レベルが高くなります。構成クラスを変更する必要がある場合は、その構成クラスが使用されているすべてのインスタンスにアクセスし、何も破損していないことを確認する必要があります。

これに対処する1つの方法は、アプリのさまざまな部分に必要な構成オブジェクトの部分を公開する複数のインターフェイスを作成することです。他のクラスが構成オブジェクトにアクセスできるようにするのではなく、インターフェースのインスタンスを必要とするクラスに渡します。このように、構成を使用するアプリの部分は、構成クラス全体ではなく、インターフェイス内のフィールドの小さなコレクションに依存します。最後に、単体テストでは、インターフェイスを実装するカスタムクラスを作成できます。

これらのアイデアをさらに検討する場合は、SOLIDの 原則、特にInterface Segregation PrincipleDependency Inversion Principleについて読むことをお勧めします。


7

関連設定のグループをインターフェイスで分離します。

何かのようなもの:

public interface INotificationEmailSettings {
   public string To { get; set; }
}

public interface IMediaFileSettings {
    public string BasePath { get; set; }
}

現在、実際の環境では、単一のクラスでこれらのインターフェイスの多くを実装します。そのクラスは、DBまたはアプリの構成、または何から取得するかもしれませんが、多くの場合、ほとんどの設定を取得する方法を知っています。

しかし、インターフェイスで分離すると、実際の依存関係がより明確になり、きめ細かくなります。これはテスト容易性の明らかな改善です。

しかし..依存関係として設定をインジェクトまたは提供することを常に強制されることを望みません。一貫性または明示性のために、私がすべき議論があります。しかし、実際には不必要な制限のようです。

そこで、静的クラスをファサードとして使用して、どこからでも設定に簡単にアクセスできるようにし、その静的クラス内でインターフェイスの実装を見つけて設定を取得します。

私は、サービスロケーションがほとんどの人からすぐに不利になることを知っていますが、コンストラクターを介した依存関係が重みであり、時にはその重みが私が耐える以上のものであることを考えてみましょう。Service Locationは、インターフェイスへのプログラミングを通じてテスト容易性を維持し、静的なシングルトンエントリポイントの利便性(慎重に測定され、適切にほとんどない状況)を提供しながら複数の実装を可能にするソリューションです。

public static class AllSettings {
    public INotificationEmailSettings NotificationEmailSettings {
        get {
            return ServiceLocator.Get<INotificationEmailSettings>();
        }
    }
}

このミックスは、すべての世界で最高のものだと思います。


3

はい、ご存知のとおり、グローバル構成オブジェクトはユニットテストを困難にします。ユニットテスト用の「シークレット」セッターを用意するのは簡単ではありませんが、非常に便利ですが、ユニットテストの作成を開始できるため、時間の経過とともにコードをリファクタリングできるようになります。

(必須参照:レガシーコードを効果的に使用する。これには、レガシーコードを単体テストするための、これに加えて非常に貴重なトリックが含まれています。)

私の経験では、グローバル構成への依存関係をできるだけ少なくすることが最善です。しかし、これは必ずしもゼロではありません-それはすべて状況に依存します。グローバル設定にアクセスし、作成して使用するオブジェクトに実際の設定プロパティを渡すいくつかの高レベルの「オーガナイザー」クラスがあれば、オーガナイザーがそれだけを行う限り、たとえばテスト可能なコード自体を含まない限りうまく機能します。これにより、既存の物理構成スキーマを完全に混乱させることなく、単体テスト可能なアプリの重要な機能のほとんどまたはすべてをテストできます。

ただし、Spring for Javaのように、これはすでに真の依存性注入ソリューションに近づいています。このようなフレームワークに移行できる場合は、結構です。しかし、実際には、特にレガシーアプリを扱う場合は、DIに対する低速で細心の注意を払ったリファクタリングが、達成可能な最良の妥協案であることがよくあります。


私はあなたのアプローチが本当に好きだったので、セッターのアイデアを「秘密」にしたり、その点で「クイックハック」と見なしたりすることはお勧めしません。ユニットテストが重要なユーザー/消費者/アプリケーションの一部であるというスタンスを採用する場合、test_属性とメソッドを持つことはもはやそれほど悪いことではありませんか?問題に対する真の解決策はDIフレームワークを採用することであることに同意しますが、あなたのような例では、レガシーコードやその他の単純なケースで作業する場合、テストコードの疎外は適用されません。
フィリップデュパノビッチ

1
@kRON、これらはレガシーコードユニットをテスト可能にするための非常に有用で実用的なトリックであり、私もそれらを広範囲に使用しています。ただし、理想的には、本番コードには、テストのみを目的として導入された機能を含めないでください。長い目で見れば、これは私がリファクタリングしているところです。
ペテルトレック

2

私の経験では、Javaの世界では、これがSpringの目的です。実行時に設定される特定のプロパティに基づいてオブジェクト(Bean)を作成および管理し、それ以外はアプリケーションに対して透過的です。Anonというtest.configファイルを使用できます。または、Springが処理する他のキー(ホスト名や環境など)に基づいてプロパティを設定するロジックを構成ファイルに含めることができます。

2番目の問題に関しては、再構築することで回避できますが、見た目ほど深刻ではありません。これがあなたの場合に意味することは、たとえば、他のさまざまなクラスが使用するグローバルなConfigオブジェクトを持たないということです。Springを介してこれらの他のクラスを構成するだけで、構成オブジェクトはありません。構成に必要なものはすべてSpringを介して取得されるため、コードでこれらのクラスを直接使用できます。


2

この質問は、実際には構成よりも一般的な問題に関するものです。「シングルトン」という言葉を読むとすぐに、そのパターンに関連するすべての問題、特にテスト容易性の問題をすぐに考えました。

シングルトンパターンは「有害と見なされます」。意味、それは常に間違ったことではありませんが、通常はそうです。何かにシングルトンパターンを使用することを検討している場合は、検討をやめてください:

  • サブクラス化する必要がありますか?
  • インターフェイスにプログラムする必要がありますか?
  • それに対して単体テストを実行する必要がありますか?
  • 頻繁に変更する必要がありますか?
  • アプリケーションは、複数の運用プラットフォーム/環境をサポートすることを目的としていますか?
  • メモリ使用量について少し心配していますか?

あなたの答えがこれらのいずれかに「はい」(そしておそらく私が考えていなかった他のいくつかのこと)なら、おそらくシングルトンを使いたくないでしょう。多くの場合、構成には、シングルトン(または、インスタンスレスクラス)が提供できる以上の柔軟性が必要です。

苦痛なしでシングルトンのほぼすべての利点が必要な場合は、SpringやCastleなどの依存性注入フレームワーク、または環境で利用可能なものを使用してください。これにより、一度宣言するだけで、コンテナは必要なものにインスタンスを自動的に提供します。


0

C#でこれを処理した1つの方法は、インスタンスの作成中にロックし、すべてのクライアントオブジェクトで使用するためにすべてのデータを一度に初期化するシングルトンオブジェクトを持つことです。このシングルトンがキー/値のペアを処理できる場合、さまざまなクライアントおよびクライアントタイプで使用するための複雑なキーを含む、あらゆる方法のデータを保存できます。動的に保ち、必要に応じて新しいクライアントデータをロードする場合は、クライアントのメインキーの存在を確認し、見つからない場合はそのクライアントのデータをロードして、メインの辞書に追加します。また、プライマリ構成シングルトンにクライアントデータのセットが含まれ、同じクライアントタイプの複数がプライマリ構成シングルトンを介してすべてアクセスされる同じデータを使用する可能性もあります。この構成オブジェクトの構成の多くは、構成クライアントがこの情報にアクセスする方法と、その情報が静的か動的かによって異なります。キー/値の構成と、必要に応じて特定のオブジェクトAPIの両方を使用しました。

私の構成シングルトンの1つは、データベースからリロードするメッセージを受信できます。この場合、2番目のオブジェクトコレクションに読み込み、コレクションをスワップするためにメインコレクションのみをロックします。これにより、他のスレッドで読み取りがブロックされるのを防ぎます。

構成ファイルからロードする場合、ファイル階層を作成できます。1つのファイルの設定により、他のどのファイルをロードするかを指定できます。このメカニズムは、それぞれ独自の構成設定を持つ複数のオプションコンポーネントを持つC#Windowsサービスで使用しました。ファイル構造パターンはファイル全体で同じでしたが、ロードされたか、プライマリファイル設定に基づいていませんでした。


0

説明しているクラスは、関数ではなくデータを除いて、神オブジェクトのアンチパターンのように聞こえます。それは問題です。何らかの理由で必要な場合にのみデータを再度読み取りながら、構成データを適切なオブジェクトに読み取って保存する必要があります。

さらに、適切でない理由でシングルトンを使用しています。シングルトンは、複数のオブジェクトが存在すると状態が悪くなる場合にのみ使用してください。この場合、シングルトンを使用するのは不適切です。複数の構成リーダーを使用しても、すぐにエラー状態になることはないからです。複数ある場合は、おそらく間違っていますが、構成リーダーを1つだけ持っている必要はありません。

最後に、このようなグローバルな状態を作成することは、データに直接アクセスする必要がある以上のクラスを許可するため、カプセル化の違反になります。


0

関連する設定の「まとまり」で多くの設定がある場合、それらをそれぞれの実装で別々のインターフェースに分割することは理にかなっていると思います-そして、DIで必要な場所にそれらのインターフェースを注入します。テスト容易性、低カップリング、SRPを獲得します。

私はこの問題について、ロス・テキーズのジョシュア・フラナガンからインスピレーションを受けました。彼はしばらく前にこの問題に関する記事を書きました

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.