私の同僚は、「ロギング/キャッシングなどが横断的な関心事である」と言ってから、対応するシングルトンをどこでも使用して進めます。それでも、彼らはIoCとDIが大好きです。
SOLI Dの原則を破ることは本当に正当な言い訳ですか?
私の同僚は、「ロギング/キャッシングなどが横断的な関心事である」と言ってから、対応するシングルトンをどこでも使用して進めます。それでも、彼らはIoCとDIが大好きです。
SOLI Dの原則を破ることは本当に正当な言い訳ですか?
回答:
番号。
SOLIDは、避けられない変化を説明するためのガイドラインとして存在します。ロギングライブラリ、ターゲット、フィルタリング、フォーマットなどを実際に変更することはありませんか?キャッシングライブラリ、ターゲット、戦略、スコーピングなどを実際に変更するつもりはありませんか?
もちろんそうですね。で非常に少なくとも、あなたはテストのためにそれらを分離するためにまともな方法でこれらの事をモックとしたいとしています。また、テストのためにそれらを分離する場合は、実際の理由で分離するというビジネス上の理由に直面する可能性が高くなります。
そして、ロガー自体が変更を処理するという引数を取得します。「ああ、ターゲット/フィルタリング/フォーマット/戦略が変更されたら、設定を変更するだけです!」それはゴミです。これらすべてを処理するGodオブジェクトがあるだけでなく、静的分析を取得しないXML(または同様の)でコードを記述しているため、コンパイル時エラーが発生せず、本当に効果的な単体テストを取得できます。
SOLIDガイドラインに違反するケースはありますか?絶対に。場合によっては(とにかく完全な書き換えを必要とせずに)変化しないことがあります。LSPのわずかな違反が最もクリーンなソリューションである場合があります。孤立したインターフェースを作成しても価値がない場合があります。
しかし、ロギングとキャッシング(およびその他のユビキタスな分野横断的な懸念事項)はそうではありません。これらは通常、ガイドラインを無視した場合に発生するカップリングと設計の問題の優れた例です。
String
たり、モジュールから除外しInt32
たりすることはあまりありませんList
。変化を計画するのが合理的かつ正気である程度だけです。そして、ほとんど明白な「コア」タイプを超えて、おそらくあなたが変えるかもしれないものを見分けることは、まさに経験と判断の問題です。
はい
これが「横断的関心事」という用語の要点です。これは、SOLID原則にきちんと適合しないものを意味します。
理想主義と現実が出会うところです。
半ばソリッドで横断的である人々は、しばしばこの精神的な挑戦に直面します。大丈夫、びっくりしないでください。すべてをSOLIDの観点に入れるよう努めますが、SOLIDが意味をなさないログやキャッシュなどの場所がいくつかあります。クロスカットはSOLIDの兄弟であり、手をつないで行きます。
HttpContextBase
(まさにこの理由で導入された)なしでWebアプリケーションを単体テストすることを想像してください。このクラスがなければ私の現実は本当にすっぱいものになることは確かです。
ロギングについてはそうだと思います。ロギングはある普及とサービス機能に一般的に無関係。ロギングフレームワークのシングルトンパターンを使用することは一般的であり、よく理解されています。そうしないと、あらゆる場所にロガーを作成して注入することになり、それは望ましくありません。
上記の問題の1つは、誰かが「しかし、どのようにロギングをテストできますか?」と言うことです。。私は、ログファイルを実際に読み取って理解できると断言する以外に、通常はロギングをテストしないと考えています。私は、ロギングがテスト見てきたときにクラスが実際にしたことを主張するために誰かを必要とするため、それが通常だ行って何かをして、彼らはそのフィードバックを得るために、ログメッセージを使用しています。むしろ、そのクラスにリスナー/オブザーバーを登録し、テストで呼び出されることをアサートします。その後、イベントロギングをそのオブザーバー内に配置できます。
ただし、キャッシュはまったく異なるシナリオだと思います。
私の2セント...
はいといいえ。
あなたは決してすべきである本当にあなたが採用の原則に違反していません。しかし、あなたの原則は常に微妙であり、より高い目標に奉仕するために採用されるべきです。したがって、適切に条件付けられた理解では、明らかな違反の一部は、「精神」または「原則全体」の実際の違反ではない可能性があります。
特に、SOLIDの原則は、多くの微妙なニュアンスを必要とすることに加えて、最終的には「機能する保守可能なソフトウェアを提供する」という目標に従属します。したがって、SOLIDの特定の原則を順守することは、SOLIDの目標と矛盾する場合、自己破滅的かつ自己矛盾的です。そして、ここで、私はしばしば、配信が保守性よりも優れていることに注意します。
それでは、SOLIDのDはどうですか?まあ、それはあなたの再利用可能なモジュールをそのコンテキストに比較的寛容にすることによって、メンテナンス性の向上に貢献します。また、「再利用可能なモジュール」を「別の異なるコンテキストで使用する予定のコードのコレクション」として定義できます。そして、それは単一の関数、クラス、クラスのセット、およびプログラムに適用されます。
もちろん、ロガーの実装を変更すると、モジュールが「別の個別のコンテキスト」になる可能性があります。
それで、2つの大きな注意点を申し上げます。
まず 、「再利用可能なモジュール」を構成するコードブロックの周りに線を引くことは、専門家の判断の問題です。そして、あなたの判断は必然的にあなたの経験に限定されます。
現在、別のコンテキストでモジュールを使用する予定がない場合は、そのモジュールに無力に依存しても大丈夫でしょう。警告への警告:あなたの計画はおそらく間違っている-しかしそれはまた良い。モジュールを次々と書くほど、「いつかまたこれが必要になる」という感覚がますます直感的で正確になります。しかし、おそらく、「すべてを可能な限りモジュール化して切り離しましたが、過剰なことはありません」と振り返ることはできないでしょう。
判断の誤りに罪悪感を覚えたら、告白に進んでください...
第二に、 反転制御は依存性の注入とは異なります。
これは、依存関係を吐き出し始めたときに特に当てはまります。依存性注入は、包括的なIoC戦略に役立つ戦術です。しかし、私は、DIがであることを主張したい低い有効性インターフェイスとアダプタを使用してのような- -いくつかの他の戦術よりも単一モジュール内からコンテキストへの曝露の点。
そして、これに少し焦点を当てましょう。なぜなら、あなたが広告nauseamを注入したとしても、インターフェースに対してコードを書く必要があるからです。異なる順序でパラメータを受け取る異なるベンダーの新しいものを使用し始めることはできませんでした。この機能は、モジュール内に存在し、依存関係を管理するための単一のサブモジュール(アダプター)を内部に持つインターフェースに対する、モジュール内のコーディングからもたらされます。Logger
Logger
Logger
また、アダプタに対してコーディングしている場合、Logger
そのアダプタにインジェクトされるか、アダプタによって検出されるかは、一般に、全体的な保守性の目標にはほとんど関係ありません。さらに重要なことは、モジュールレベルのアダプターを使用している場合、おそらくそれを何かに挿入するのはばかげていることです。モジュール用に書かれています。
tl; dr-原則を使用している理由を考慮せずに、原則について悩まないでください。そして、より現実的には、モジュールごとにをビルドするだけAdapter
です。「モジュール」の境界線をどこに描くかを決めるときは、判断してください。各モジュール内から、を直接参照してくださいAdapter
。そして、注入、必ず実際のロガーにAdapter
-しかし、ではない、それを必要とするかもしれないあらゆる小さい事に。
ロギングは常にシングルトンとして実装されるべきであるという考えは、多くの場合それが牽引力を得たと言われている嘘の1つです。
最新のオペレーティングシステムに関する限り、出力の性質に応じて複数の場所にログを記録することをお勧めします。
システム設計者は、新しいソリューションに盲目的に組み込む前に、過去のソリューションの有効性に絶えず疑問を呈する必要があります。彼らがそのような勤勉さを実行していなければ、彼らは仕事をしていない。
本当にログを記録するのは特別な場合です。
@Telastynの書き込み:
ロギングライブラリ、ターゲット、フィルタリング、フォーマットなどを実際に変更することはありませんか?
ロギングライブラリを変更する必要があると予想される場合は、ファサードを使用する必要があります。つまり、Javaの世界にいるならSLF4Jです。
残りについては、適切なロギングライブラリが、ロギングの場所、フィルタリングされるイベント、ロガー構成ファイルおよび(必要に応じて)カスタムプラグインクラスを使用したログイベントのフォーマット方法の変更を処理します。多数の市販の代替品があります。
要するに、これらはロギングのために解決された問題です...したがって、それらを解決するために依存性注入を使用する必要はありません。
DIが(標準のログ方法よりも)有益である唯一のケースは、アプリケーションのログを単体テストの対象にする場合です。ただし、ほとんどの開発者は、ロギングはクラス機能の一部ではなく、テストが必要なものではないと言うでしょう。
@Telastynの書き込み:
そして、ロガー自体が変更を処理するという引数を取得します。「ああ、ターゲット/フィルタリング/フォーマット/戦略が変更されたら、設定を変更するだけです!」それはゴミです。これらすべてを処理するGodオブジェクトがあるだけでなく、静的分析を取得しないXML(または同様の)でコードを記述しているため、コンパイル時エラーが発生せず、本当に効果的な単体テストを取得します。
私はそれが非常に理論的なリポストであることを恐れています。実際には、ほとんどの開発者とシステムインテグレーターは、設定ファイルを介してロギングを設定できるという事実を好みます。そして、彼らはモジュールのロギングを単体テストすることを期待されていないという事実を好みます。
確かに、ロギング設定を詰め込んだ場合、問題が発生する可能性がありますが、それらは起動時にアプリケーションが失敗するか、ロギングが多すぎる/少なすぎるとして現れます。1)これらの問題は、構成ファイルの間違いを修正することで簡単に修正できます。2)別の方法は、ログレベルを変更するたびに、完全なビルド/分析/テスト/展開サイクルを実行することです。それは受け入れられません。
はい 、 いいえ!
はい:すべてのサブシステムが同じ共通共有シングルトンに依存するのではなく、異なるサブシステム(またはセマンティックレイヤーまたはライブラリ、またはモジュールバンドリングの他の概念)がそれぞれ初期化中に(同じまたは)潜在的に異なるロガーを受け入れることが合理的だと思います。
しかしながら、
いいえ:同時に、すべての小さなオブジェクトのロギングをパラメーター化することは(コンストラクターまたはインスタンスメソッドによって)理不尽です。不要で無意味な肥大化を避けるために、より小さなエンティティは、それらを囲むコンテキストのシングルトンロガーを使用する必要があります。
これは、レベルのモジュール性を考える多くの理由の1つです。メソッドはクラスにバンドルされ、クラスはサブシステムやセマンティックレイヤーにバンドルされます。これらの大きなバンドルは、価値のある抽象化ツールです。モジュールの境界内では、それらを越える場合とは異なる考慮事項を与える必要があります。
まず、強力なシングルトンキャッシュで始まり、次に表示されるのは、グローバルステート、非記述的なclass
esのAPI、およびテストできないコードを導入するデータベースレイヤーの強力なシングルトンです。
データベースにシングルトンを使用しないことにした場合、キャッシュにシングルトンを使用することはおそらく良い考えではありません。
クラスでシングルトンを使用すると、特定の量の依存関係を持つクラスが、理論的には無限の数を持つクラスに変わります。これは、静的メソッドの背後に実際に隠されているものがわからないためです。
過去10年間にプログラミングに費やしてきたのは、ロギングロジック(シングルトンとして記述されていた)を変更しようとする試みを目の当たりにしたケースは1つだけでした。そのため、依存関係の注入は大好きですが、ロギングはそれほど大きな関心事ではありません。一方、キャッシュは、常に依存関係として作成します。
はい、いいえ、しかしほとんどはいいえ
会話の大部分は、静的インスタンスと注入されたインスタンスに基づいていると思います。ロギングが私が想定しているSRPを破ることを提案している人はいないでしょうか?私たちは主に「依存性反転の原理」について話している。Tbh私は、Telastynの無回答にほぼ同意します。
いつ静的を使用しても大丈夫ですか?明らかにそれは時々大丈夫だからです。「はい」の回答は抽象化のメリットを示し、「いいえ」の回答はそれらがあなたが支払うものであることを示します。仕事が難しい理由の1つは、書き留めてすべての状況に適用できる答えが1つもないことです。
取る:
Convert.ToInt32("1")
私はこれを好む:
private readonly IConverter _converter;
public MyClass(IConverter converter)
{
Guard.NotNull(converter)
_converter = conveter
}
....
var foo = _converter.ToInt32("1");
どうして? 変換コードを交換する柔軟性が必要な場合は、コードをリファクタリングする必要があることを受け入れています。私はこれをあざけることができないことを受け入れています。シンプルさと簡潔さはこの取引の価値があると思います。
場合は、スペクトルのもう一方の端を見ていIConverter
たSqlConnection
、私はかなり静的呼び出しとしてそれを見て恐怖されるだろう。その理由は明らかです。私SQLConnection
は、アプリケーションでa がかなり「横断的」である可能性があることを指摘したいと思います。そのため、これらの正確な単語は使用しませんでした。
以上のようにログインしていますSQLConnection
かConvert.ToInt32
?「SQLConnection」のようなものだと思います。
Loggingをモックする必要があります。外の世界と話します。を使用してメソッドを作成するときConvert.ToIn32
、クラスの別のテスト可能な出力を計算するツールとして使用しています。Convert
「1」+「2」==「3」であることを確認するときに、正しく呼び出されたことを確認する必要はありません。ロギングは異なり、クラスの完全に独立した出力です。私はそれがあなた、サポートチーム、そしてビジネスにとって価値のあるアウトプットだと思っています。ロギングが正しくない場合、クラスは機能しません。そのため、ユニットテストはパスしません。クラスのログをテストする必要があります。これは致命的な議論だと思います。本当にここでやめることができます。
私もそれが変わる可能性が高いと思います。優れたロギングは文字列を出力するだけでなく、アプリケーションが実行していることのビューです(イベントベースのロギングの大ファンです)。基本的なロギングが非常に複雑なレポートUIに変わるのを見てきました。ロギングが次のように見える場合_logger.Log(new ApplicationStartingEvent())
とそうでない場合は、明らかにこの方向に進むのがずっと簡単Logger.Log("Application has started")
です。これは決して起こらないかもしれない未来の目録を作成していると主張するかもしれません、これは判断の呼び出しであり、私はそれが価値があると思います。
実際、私の個人的なプロジェクトでは_logger
、アプリケーションが何をしていたのかを把握するために純粋にを使用して非ロギングUIを作成しました。これは、アプリケーションが何をしているのかを理解するためにコードを書く必要がなかったことを意味し、最終的に堅実なロギングになりました。伐採に対する私の姿勢がシンプルで変わらないものであったなら、その考えは私には起こらなかったでしょう。
ロギングの場合、Telastynに同意します。
Semantic Logging Application Block
。皮肉なことに、MSの「パターンと実用」チームによって作成されたコードのほとんどのように、それを使用しないでください。これはアンチパターンです。
最初の横断的関心事は主要な構成要素ではなく、システム内の依存関係として扱われるべきではありません。たとえば、ロガーが初期化されていないか、キャッシュが機能していない場合、システムは動作するはずです。どのようにしてシステムの結合性と凝集性を低下させますか?それが、SOLIDがオブジェクト指向システム設計で登場するところです。
オブジェクトをシングルトンとして保持することは、SOLIDとは関係ありません。これがオブジェクトのライフサイクルであり、オブジェクトをメモリ内で存続させる期間です。
初期化するために依存関係を必要とするクラスは、提供されたクラスインスタンスがシングルトンであるか一時的なものであるかを知らないはずです。しかし、tldr; すべてのメソッドまたはクラスでLogger.Instance.Log()を記述している場合、問題のあるコード(コードの匂い/ハードカップリング)が非常に厄介です。これは人々がSOLIDを乱用し始める瞬間です。そしてOPのような仲間の開発者は、このような本物の質問をし始めます。