web-dev-qa-db-ja.com

いわゆる「横断的関心事」は、SOLID / DI / IoCを破る有効な言い訳ですか?

私の同僚は、「ロギング/キャッシングなどは分野横断的な関心事である」と述べてから、対応するシングルトンをどこでも使用しています。しかし、彼らはIoCとDIを愛しています。

SOLI [〜#〜] d [〜#〜]原則を破るのは本当に有効な言い訳ですか?

42
Den

番号

SOLIDは、避けられない変化を説明するためのガイドラインとして存在します。あなたは本当にロギングライブラリ、ターゲット、フィルタリング、またはフォーマットを変更するつもりはありませんか?あなたは本当にキャッシュライブラリ、ターゲット、戦略、またはスコープ、または...を変更しないつもりですか?

もちろんそうですね。 非常に少なく、これらを適切にモックしてテスト用に分離する必要があります。また、テストのためにそれらを分離したい場合は、実際の理由でそれらを分離したいビジネス上の理由に遭遇する可能性があります。

そして、ロガーitselfが変更を処理するという引数を取得します。 「ああ、ターゲット/フィルタリング/フォーマット/戦略が変更されたら、構成を変更するだけです!」それがゴミです。これらすべてを処理するGodオブジェクトがあるだけでなく、静的分析が行われず、コンパイル時エラーが発生せず、XMLでコード(または類似のコード)を記述しています。本当に効果的な単体テストを取得します。

SOLIDガイドラインに違反するケースはありますか?絶対にあります。時々、物事が変更されない(とにかく完全な書き換えを必要としない) )LSPのわずかな違反が最もクリーンなソリューションである場合があります。

しかし、ロギングとキャッシング(および他のユビキタスな横断的関心事)はそれらのケースではありません。これらは通常、ガイドラインを無視したときに発生するカップリングと設計の問題の優れた例です。

41
Telastyn

はい

これが「横断的関心事」という用語の要点です。これは、SOLIDの原則にうまく適合しないものを意味します。

これは理想主義が現実と出会うところです。

SOLIDやクロスカッティングが初めての人や、横断的なことがこの精神的な課題に出くわすことがよくあります。大丈夫です。おかしくなりません。すべてをSOLIDの観点から取り入れようと努力しますが、いくつかの場所があります。ロギングとキャッシングのようにSOLIDは意味がありません。横断的切断はSOLIDの兄弟であり、彼らは手を取り合っています。

39
Klom Dark

ロギングについてはそうだと思います。ロギングはpervasiveであり、通常はサービス機能とは無関係です。ロギングフレームワークのシングルトンパターンを使用することは一般的でよく理解されています。そうしないと、ロガーを作成して注入することになりますeverywhereで、それは必要ありません。

上記の1つの問題は、誰かが'しかし、どのようにしてロギングをテストできるか?'と言うことです。私の考えでは、実際にログファイルを読み取って理解できると断言するだけでなく、通常はログをテストしません。ロギングがテストされたのを見たとき、それは通常、誰かがクラスが実際にdone何かを持っていることを主張する必要があり、そのフィードバックを得るためにログメッセージを使用しているためです。私はむしろそのクラスにいくつかのリスナー/オブザーバーを登録し、それが呼び出されることをテストでアサートします。その後、そのオブザーバー内にイベントログを配置できます。

ただし、キャッシングは完全に異なるシナリオだと思います。

25
Brian Agnew

私の2セント...

はい、いいえ。

本当に採用した原則に違反しないでください。しかし、あなたの原則は常に微調整され、より高い目標に向けて採用されるべきです。したがって、適切に条件付けられた理解があれば、一部のapparent違反はactual「精神」または「原則としての全体」の違反ではない可能性があります。

特にSOLID原則は、多くのニュアンスを必要とすることに加えて、最終的には「機能する保守可能なソフトウェアを提供する」という目標にかなうものです。したがって、特定のSOLID原則は、SOLIDの目標と矛盾する場合、自己破壊的で自己矛盾します。ここで、私はしばしばdelivering切り札maintainabilityに注意します。

つまり、[〜#〜] d [〜#〜]についてはどうでしょう[〜#〜] solid [〜#〜] =まあ、それはあなたの再利用可能なモジュールをそのコンテキストに比較的にとらわれないものにすることによって保守性の向上に貢献します。また、「再利用可能なモジュール」を「別のコンテキストで使用する予定のコードのコレクション」と定義できます。これは、単一の関数、クラス、クラスのセット、およびプログラムに適用されます。

そしてはい、ロガーの実装を変更すると、モジュールが「別の異なるコンテキスト」に置かれる可能性があります。

だから、私に提供させてください私の2つの大きな警告

最初に:「再利用可能なモジュール」を構成するコードブロックの周りに線を引くことは、専門家の判断の問題です。そして、あなたの判断は必ずしも経験に限定されます。

currently別のコンテキストでモジュールを使用する予定がない場合、それはおそらく無力に依存していても問題ありません。警告への警告:あなたの計画はおそらく間違っています-しかしそれはalso OKです。モジュールを次から次へと長く書くほど、「いつかまた必要になる」という感覚がますます直感的で正確になります。しかし、おそらくneverは、「すべてを可能な限り最大限にモジュール化し、切り離したので、-しかし過剰ではない」と振り返ることができるでしょう。

判断の誤りについて罪を犯していると感じた場合は、自白してから先に進んでください...

2番目:反転制御等しくない依存関係の注入

これは依存性の広告を吐き出すを開始するときに特に当てはまります。依存性注入は、包括的なIoC戦略にとって有用な戦術です。ただし、DIは他のいくつかの戦術よりも有効性が低いであると主張します-インターフェースやアダプターの使用など--単一モジュール内からのコンテキストへの露出のポイント。

そして、これに本当に少し集中しましょう。 場合でもLoggerad nauseamを注入するため、Loggerインターフェイスに対してコードを記述する必要があります。パラメータを別の順序で取得する別のベンダーの新しいLoggerの使用を開始できませんでした。その能力は、モジュール内で、存在するインターフェイスに対してコーディングすることで得られますモジュール内であり、依存関係を管理するために内部に単一のサブモジュール(アダプター)を持っています。

また、アダプタに対してコーディングしている場合、Loggerがそのアダプタに挿入されているか、アダプタによって検出されているかは、一般に、全体的な保守性の目標にはほとんど意味がありません。さらに重要なのは、モジュールレベルのアダプターがある場合、それを何かに挿入するのはおそらくばかげたことです。 forモジュールと書かれています。

tl; dr-原則を使用している理由を考慮せずに、原則について混乱するのをやめます。そして、より実際的には、モジュールごとにAdapterを作成するだけです。「モジュール」の境界をどこに描くかを決定するときは、判断に基づいてください。各モジュール内から、Adapterを直接参照してください。そして確かに、realロガーをAdapter-に注入します-しかしnotを必要とする可能性のあるすべての小さなものに注入します。

13
svidgen

ロギングをalwaysをシングルトンとして実装する必要があるという考えは、頻繁に説得力があると言われている嘘の1つです。

出力の性質 によっては、最新のオペレーティングシステムが使用されている限り、複数の場所にログを記録したい場合があることが認識されています。

システム設計者は、新しいソリューションに盲目的に組み込む前に、過去のソリューションの有効性を常に疑問視する必要があります。彼らがそのような勤勉さを実行していなければ、彼らは仕事をしていません。

8
Robbie Dee

真にロギングすることは特別なケースです。

@Telastynの書き込み:

ロギングライブラリ、ターゲット、フィルタリング、フォーマットなどを変更するつもりはありませんか?

ロギングライブラリを変更する必要があると思われる場合は、ファサードを使用する必要があります。つまり、Javaの世界にいる場合はSLF4Jです。

その他については、適切なロギングライブラリが、ロギングの場所、フィルタリングされるイベント、ロガー構成ファイルと(必要な場合は)カスタムプラグインクラスを使用してログイベントをフォーマットする方法を変更します。いくつかの既製の選択肢があります。

簡単に言うと、これらは解決された問題...ロギングについて...なので、依存性注入を使用してそれらを解決する必要はありません。

(標準のロギングアプローチよりも)DIが有益な唯一のケースは、アプリケーションのロギングを単体テストの対象にする場合です。ただし、ほとんどの開発者は、ロギングはクラス機能の一部ではなく、テストが必要なものではないと言うのではないかと思います。

@Telastynの書き込み:

次に、ロガー自体が変更を処理するという引数を取得します。 「ああ、ターゲット/フィルタリング/フォーマット/戦略が変更されたら、構成を変更するだけです!」それがゴミです。これらすべてを処理するGodオブジェクトがあるだけでなく、静的分析が行われず、コンパイル時エラーが発生せず、XMLでコード(または類似のコード)を記述しています。本当に効果的な単体テストを取得します。

それは非常に理論的なリポストだと思います。実際には、ほとんどの開発者とシステムインテグレータは、設定ファイルを介してロギングを設定できるという事実を好みます。また、モジュールのロギングを単体テストすることは期待されていないという事実も好きです。

確かに、ログ設定を詰め込むと問題が発生する可能性がありますが、起動時にアプリケーションが失敗するか、ログが多すぎる/少なすぎるという形で現れます。 1)これらの問題は、設定ファイルの誤りを修正することで簡単に修正できます。 2)代替手段は、ロギングレベルに変更を加えるたびに、完全なビルド/分析/テスト/展開サイクルです。それは許されません。

4
Stephen C

はい、いいえ、ただしほとんどいいえ

私は会話のほとんどが静的インスタンスと注入されたインスタンスに基づいていると思います。ロギングが私が想定しているSRPを壊すことを提案している人はいません私たちは主に「依存関係逆転原理」について話している。 Tbh Telastynの答えにほとんど同意します。

staticsをいつ使用しても大丈夫ですか?明らかに大丈夫な場合があるためです。抽象化の利点についての「はい」の回答ポイントと「いいえ」の回答は、それらがあなたが支払うものであることを示しています。あなたの仕事が難しい理由の1つは、書き留めてすべての状況に適用できる答えが1つないことです。

取る:Convert.ToInt32("1")

私はこれを好む:

_private readonly IConverter _converter;

public MyClass(IConverter converter)
{
   Guard.NotNull(converter)
   _converter = conveter
}

.... 
var foo = _converter.ToInt32("1");
_

理由変換コードを入れ替える柔軟性が必要な場合は、コードをリファクタリングする必要があることを受け入れています。私はこれをあざけることができないことを受け入れています。シンプルさと簡潔さはこの取引に値すると思います。

スペクトルの反対側を見ると、IConverterSqlConnectionだったとしたら、それを静的な呼び出しとして見るのはかなり恐ろしいことです。失敗の理由は明らかです。 SQLConnectionは、アプリケーションではかなり「横断的」である可能性があるため、これらの正確な単語は使用しなかったでしょう。

ロギングはSQLConnectionまたは_Convert.ToInt32_?のようなものですか私は 'SQLConnection`のようなものを言っています。

ロギングをモックする必要があります。外の世界と対話します。 _Convert.ToIn32_を使用してメソッドを作成するとき、私はそれを、クラスの別のテスト可能な出力を計算するためのツールとして使用しています。 「1」+「2」==「3」であることを確認するときにConvertが正しく呼び出されたことを確認する必要はありません。ロギングは異なり、それはクラスの完全に独立した出力です。私はそれがあなた、サポートチーム、そしてビジネスにとって価値のあるアウトプットだと思っています。ロギングが適切でない場合、クラスは機能しないため、単体テストはパスしません。 クラスログの内容をテストする必要があります。これはキラー引数だと思います。ここで止めることができます。

また、かなり変化する可能性が高いと思います。優れたロギングは、文字列を出力するだけでなく、アプリケーションが何をしているかを表示するものです(私はイベントベースのロギングの大ファンです)。基本的なロギングが非常に手の込んだレポートUIに変わるのを見てきました。ロギングが_logger.Log(new ApplicationStartingEvent())のように見え、Logger.Log("Application has started")のように見えない場合、明らかにこの方向に進む方がはるかに簡単です。これは決して起こらないかもしれない未来のためのインベントリを作成していると主張する人もいるでしょう。これは判断の呼びかけであり、たまたま価値があると思います。

実際、私の個人的なプロジェクトでは、__logger_を純粋に使用して非ロギングUIを作成し、アプリケーションが何をしているかを理解しました。これは、アプリケーションが何をしているかを理解するためにコードを記述する必要がないことを意味し、結局、堅実なロギングになりました。伐採への私の態度は、それが単純で不変であるというものだったとしたら、その考えは私には思いもよらなかったでしょう。

したがって、ロギングの場合についてはTelastynに同意します。

3
Nathan Cooper

最初の横断的関心事は主要な構成要素ではなく、システムの依存関係として扱うべきではありません。システムは、たとえば、ロガーが初期化されていないか、キャッシュが機能していません。どのようにしてシステムの結合性と凝集性を低下させますか?ここでSOLIDがOOシステム設計に登場します。

オブジェクトをシングルトンとして保持することは、SOLIDとは関係ありません。これは、オブジェクトをメモリ内に存続させる期間をオブジェクトのライフサイクルにしたものです。

初期化に依存関係が必要なクラスは、提供されたクラスインスタンスがシングルトンか一時的かを認識してはなりません。しかし、tldr;すべてのメソッドまたはクラスでLogger.Instance.Log()を作成している場合、問題のあるコード(コードのにおい/ハードカップリング)は本当に厄介なコードです。これは人々がSOLIDを乱用し始める瞬間です。そして、OPのような仲間の開発者は、このような本物の質問をし始めます。

3
vendettamit

はいおよびいいえ

はい:異なるサブシステム(またはセマンティックレイヤー、ライブラリ、またはモジュールバンドリングの他の概念)が、すべてのサブシステムに依存するのではなく、初期化中に(同じまたは)潜在的に異なるロガーを受け入れるのは理にかなっていると思います同じ共通の共有シングルトン

しかしながら、

いいえ:(コンストラクターまたはインスタンスメソッドによって)すべての小さなオブジェクトのロギングをパラメーター化することは同時に不合理です。不要で無意味な肥大化を回避するために、小さなエンティティは、それらを含むコンテキストのシングルトンロガーを使用する必要があります。


これは、レベルのモジュール性を考える多くの理由の1つです。メソッドはクラスにバンドルされ、クラスはサブシステムやセマンティックレイヤーにバンドルされます。これらの大きなバンドルは、抽象化の貴重なツールです。モジュールの境界内では、交差する場合とは異なる考慮事項を与える必要があります。

3
Erik Eidt

まず、強力なシングルトンキャッシュから始めます。次に目にするのは、データベースレイヤーの強力なシングルトンで、グローバルな状態、classesの説明のないAPI、およびテスト不可能なコードが導入されています。

データベースにシングルトンを持たないことを決定した場合、おそらくキャッシュにシングルトンを持たせることはお勧めできません。結局のところ、それらは非常によく似た概念であるデータストレージを表し、異なるメカニズムのみを使用しています。

クラスでシングルトンを使用すると、特定の量の依存関係を持つクラスが理論的にはのクラスに変わります。静的メソッドの背後に実際に隠されているものがわからないためです。

過去10年間、プログラミングに費やしてきましたが、ロギングロジックを変更しようとする試みが1つしかありませんでした(それがシングルトンとして記述された)。したがって、依存関係の注入は大好きですが、ロギングはそれほど大きな問題ではありません。一方、キャッシュは、常に依存関係として作成します。

3
Andy

この問題は、継承と特性(一部の言語ではミックスインとも呼ばれます)を組み合わせて使用​​して解決しました。特性は、この種の横断的な問題を解決するのに非常に便利です。それは通常言語機能ですが、本当の答えは言語機能に依存しているということです。

2
RibaldEddie