実装の隣のロギングはSRP違反ですか?


19

アジャイルなソフトウェア開発とすべての原則(SRP、OCPなど)を考えるとき、ロギングをどのように扱うかを自問します。

実装の隣のロギングはSRP違反ですか?

yes実装はロギングなしで実行することもできるはずだからです。それでは、より良い方法でロギングを実装するにはどうすればよいですか?いくつかのパターンを確認し、ユーザー定義の方法で原則に違反するのではなく、原則に違反することが知られているパターンを使用する最良の方法は、デコレータパターンを使用することであるという結論に達しました。

SRP違反のない完全に多数のコンポーネントがあり、ロギングを追加するとします。

  • コンポーネントA
  • コンポーネントBはAを使用します

Aのロギングが必要なので、Aで装飾された別のコンポーネントDを作成し、両方ともインターフェースIを実装します。

  • インターフェースI
  • コンポーネントL(システムのロギングコンポーネント)
  • コンポーネントAはIを実装します
  • コンポーネントDはIを実装し、Aを装飾/使用し、ロギングにLを使用します
  • コンポーネントBはIを使用します

利点:-ロギングなしでAを使用できます-Aをテストすると、ロギングモックが不要になります-テストが簡単になります

欠点:-より多くのコンポーネントとより多くのテスト

これはもう1つの公開討論の質問のように思えますが、デコレータやSRP違反よりも優れたログ戦略を誰かが使用しているかどうかを実際に知りたいです。デフォルトのNullLoggerであり、syslogロギングが必要な場合、実行時に実装オブジェクトを変更する静的シングルトンロガーはどうでしょうか。



すでに読んでいますが、答えは満足のいくものではありません。ごめんなさい。
Aitch


@MarkRogersはその興味深い記事を共有してくれてありがとう。ボブおじさんは、「きれいなコード」で、素敵なSRPコンポーネントが同じ抽象レベルで他のコンポーネントを処理していると言います。私にとっては、コンテキストが大きすぎる可能性があるため、その説明は理解しやすいです。しかし、質問に答えることはできません。ロガーのコンテキストまたは抽象化レベルは何ですか?
-Aitch

3
「私の答えではありません」または「答えが満足のいくものではありません」は少し否定的です。あなたは熟考かもしれないものを、具体的あっけなくされます(つまり、具体的にあなたの質問についてユニークである何その答えは?によって満たされていなかったていないものを要求?)、その後、この要件/ユニークな点は明確に説明されていることを確認するためにあなたの質問を編集します。目的は、質問を編集してより明確で焦点を絞るようにすることであり、質問が異なる/正当な理由なく閉じられるべきではないことを主張する定型句を要求することではありません。(他の答えにコメントすることもできます。)
DW

回答:


-1

はい、それはロギングが横断的な関心事であるため、SRPの違反です

正しい方法は、ログをロガークラス(インターセプション)に委任することです。この唯一の目的は、ログを記録することです(SRPに準拠)。

良い例については、このリンクを参照してください:https : //msdn.microsoft.com/en-us/library/dn178467%28v=pandp.30%29.aspx

以下に短い例を示します。

public interface ITenantStore
{
    Tenant GetTenant(string tenant);
    void SaveTenant(Tenant tenant);
}

public class TenantStore : ITenantStore
{
    public Tenant GetTenant(string tenant)
    {....}

    public void SaveTenant(Tenant tenant)
    {....}
} 

public class TenantStoreLogger : ITenantStore
{
    private readonly ILogger _logger; //dep inj
    private readonly ITenantStore _tenantStore;

    public TenantStoreLogger(ITenantStore tenantStore)
    {
        _tenantStore = tenantStore;
    }

    public Tenant GetTenant(string tenant)
    {
        _logger.Log("reading tenant " + tenant.id);
        return _tenantStore.GetTenant(tenant);
    }

    public void SaveTenant(Tenant tenant)
    {
        _tenantStore.SaveTenant(tenant);
        _logger.Log("saving tenant " + tenant.id);
    }
}

利点が含まれます

  • ログなしでこれをテストできます-本当のユニットテスト
  • 実行時でも簡単にロギングのオン/オフを切り替えることができます
  • TenantStoreファイルを変更することなく、他の形式のログをログに置き換えることができます。

素敵なリンクをありがとう。そのページの図1は、実際に私がお気に入りのソリューションと呼ぶものです。横断的な関心事(ロギング、キャッシュなど)とデコレーターパターンのリストは最も一般的なソリューションであり、私の考えに完全に間違いがないことを嬉しく思いますが、より大きなコミュニティはその抽象化とインラインロギングを削除したいと思います。
-Aitch

2
_logger変数をどこにも割り当てていません。コンストラクターインジェクションの使用を計画していて、忘れていましたか?その場合、おそらくコンパイラの警告が表示されます。
user2023861

27
TenantStoreがN + 1クラスを必要とする汎用LandgerでDIPされる代わりに(LandlordStore、FooStore、BarStoreなどを追加する場合)、TenantStoreLoggerがTenantStoreでDIPされ、FooStoreLoggerがFooStoreでDIPされるなど... 2Nクラスが必要です。私が知る限り、利益ゼロです。 ロギングなしのユニットテストを実行する場合は、NullLoggerを構成するだけでなく、Nクラスを再調整する必要があります。IMO、これは非常に貧弱なアプローチです。
user949300

6
ロギングを必要とするすべてのクラスに対してこれを行うと、コードベースの複雑さが大幅に増大します(ロギングを行うクラスがほとんどないため、これ以上横断的な問題とは言えません)。最終的に、単一の責任原則が作成されたすべてのものに反する、維持する多数のインターフェースのために、コードの保守性が低下します。
jpmc26

9
ダウン投票。テナントクラスからロギングの懸念を削除しましたTenantStoreLoggerが、変更するたびに変更されTenantStoreます。最初のソリューションよりも懸念を分離することはありません。
ローランラリザ

61

私はあなたがSRPをあまりにも真剣に受け止めていると言います。コードがきちんと整理されていて、ロギングがSRPの唯一の「違反」である場合、他のすべてのプログラマーの99%を上回っています。

SRPのポイントは、異なることを行うコードがすべて混同される恐ろしいスパゲッティコードを避けることです。ロギングと機能コードを組み合わせても、警告音は鳴りません。


19
@Aitch:クラスへのログインをハードワイヤーするか、ロガーにハンドルを渡すか、まったくログを記録しないかを選択できます。他のすべてを犠牲にしてSRPについて非常に厳しくするつもりなら、何もログに記録しないことをお勧めします。あなたのソフトウェアが何をしているのかについて知る必要があるものは何でも、デバッガーで打ち消すことができます。SRPのPは、「決して破られてはならない自然の物理法則」ではなく、「原理」を表しています。
Blrfl

3
@Aitch:クラスのロギングを何らかの要件までさかのぼることができなければ、YAGNIに違反しています。ロギングがテーブルで行われる場合、クラスが必要とする他のすべての場合と同じように、有効なロガーハンドルで、できればテストに合格したクラスのものを提供します。実際のログエントリを生成するのか、それをビットバケットにダンプするのかは、クラスのインスタンスをインスタンス化したものの問題です。クラス自体は気にする必要はありません。
Blrfl

3
@Aitchユニットテストに関する質問に答えるにDo you mock the logger?は、次のとおりです。ILoggerロガーが行うことを定義するインターフェースが必要です。テスト対象のコードには、ILogger指定したコードが挿入されます。テストには、がありclass TestLogger : ILoggerます。これについての素晴らしいところはTestLogger、最後に記録された文字列やエラーなどを公開できることです。テストでは、テスト対象のコードが正しくログに記録されていることを確認できます。たとえば、テストは、テストがログに記録されているUserSignInTimeGetsLogged()かどうかTestLoggerを確認できます。
CurtisHx

5
99%は少し低いようです。おそらく、すべてのプログラマーの100%よりも優れているでしょう。
ポールドレーパー

2
正気のために+1。この種の考え方をもっと必要とします。言葉や抽象的な原則に焦点を当てるのではなく、保守可能なコードベースを用意することに重点を置きます。
jpmc26

15

いいえ、SRPの違反ではありません。

ログに送信するメッセージは、周囲のコードと同じ理由で変化するはずです。

SRP違反とは、特定のライブラリを使用してコードに直接ログインすることです。ロギングの方法を変更することにした場合、SRPはビジネスコードに影響を与えるべきではないと述べています。

Logger実装コードから何らかの抽象化にアクセスできる必要があります。実装で言うべきことは、「このメッセージをログに送信する」ことだけです。ロギングの正確な方法(タイムスタンプを付けても)を決定することは、実装の責任ではありません。

その場合、実装は、メッセージの送信先のロガーがであるかどうかも知らないはずNullLoggerです。

そうは言った。

横断的関心事としての伐採を早すぎないようにします。実装コードで発生する特定のイベントをトレースするためのログの発行は、実装コードに属します。

横断的な関心事であるOTOHとは、実行トレースです。すべてのメソッドでロギングが開始および終了します。これを行うにはAOPが最適です。


ロガーメッセージが「ログインユーザーxyz」であり、タイムスタンプなどを付加するロガーに送信されるとします。「ログイン」が実装にとって何を意味するか知っていますか?Cookieまたはその他のメカニズムでセッションを開始していますか?ログインを実装するにはさまざまな方法があると思うので、実装を変更することは、ユーザーがログインするという事実とは論理的に何の関係もありません。それは、同じことを行う異なるコンポーネント(OAuthLogin、SessionLogin、Basic Login同じロガーで装飾されたインターフェースとして。
Aitch

「ログインユーザーxyz」というメッセージの意味によって異なります。ログインが成功したという事実をマークする場合、ログへのメッセージの送信はログインのユースケースに属します。ログイン情報を文字列(OAuth、セッション、LDAP、NTLM、指紋、ハムスターホイール)として表す特定の方法は、資格情報またはログイン戦略を表す特定のクラスに属します。削除する必要はありません。この特定のケースは、横断的な関心事ではありません。ログインのユースケースに固有です。
ローランラリッツァ

7

ロギングはしばしば分野横断的な懸念事項と考えられているため、AOPを使用してロギングを実装から分離することをお勧めします。

インターセプターまたは何らかのAOPフレームワーク(JavaのAspectJなど)を使用してこれを実行する言語に応じて。

問題は、これが実際に面倒な価値があるかどうかです。この分離により、プロジェクトの複雑さが増す一方で、メリットはほとんどないことに注意してください。


2
私が見たAOPコードのほとんどは、すべてのメソッドのすべての入力および終了ステップを記録することに関するものでした。一部のビジネスロジックパーツのみを記録します。したがって、注釈付きメソッドのみをログに記録することは可能かもしれませんが、AOPはスクリプト言語と仮想マシン環境でしか存在できません。たとえば、C ++では不可能です。私はAOPのアプローチにあまり満足していないことを認めていますが、おそらくより明確な解決策はありません。
Aitch

1
@Aitch。「C ++は不可能です。」:「aop c ++」を検索すると、それに関する記事が表示されます。「...私が見たAOPコードは、すべてのメソッドのすべての開始および終了ステップを記録することに関するものでした。ビジネスロジックの一部のみを記録したいのです。」Aopでは、パターンを定義して、変更するメソッドを見つけることができます。すなわち、名前空間「my.busininess。*」のすべてのメソッド
k3b

1
特に、ログに興味深い情報、つまり、例外スタックトレースに含まれるよりも多くの情報を含める場合は、ロギングは横断的な関心事ではありません。
ローランラリッツァ

5

これは問題ありません。かなり標準的なロギングデコレータについて説明しています。あなたが持っている:

コンポーネントL(システムのロギングコンポーネント)

これには1つの責任があります:渡される情報を記録する。

コンポーネントAはIを実装します

これには、インターフェースIの実装を提供するという1つの責任があります(つまり、Iが適切にSRPに準拠していると仮定します)。

これは重要な部分です。

コンポーネントDはIを実装し、Aを装飾/使用し、ロギングにLを使用します

そのように述べられたとき、それは複雑に聞こえますが、このように見ます:コンポーネントDは、AとLを一緒にするという1つのことを行います。

  • コンポーネントDはログに記録しません。それをLに委任します
  • コンポーネントDはI自体を実装しません。それをAに委任します

コンポーネントD の唯一の責任は、Aが使用されたときにLに通知されるようにすることです。AとLの実装は両方とも別の場所にあります。これは完全にSRPに準拠しているだけでなく、OCPのきちんとした例であり、デコレーターのかなり一般的な使用法です。

重要な注意点: DがロギングコンポーネントLを使用する場合、ロギング方法を変更できる方法で使用する必要があります。これを行う最も簡単な方法は、Lによって実装されるインターフェイスILを持つことです。

  • コンポーネントDはILを使用してログを記録します。Lのインスタンスが提供されます
  • コンポーネントDはIを使用して機能を提供します。Aのインスタンスが提供されます
  • コンポーネントBはIを使用します。Dのインスタンスが提供されます

そうすれば、他のものに直接依存するものは何もないので、簡単に交換できます。これにより、変更への適応が簡単になり、システムの一部を簡単にモックできるため、ユニットテストを実行できます。


私は実際には、ネイティブ委任をサポートしているC#のみを知っています。それが私が書いD implements Iた理由です。ご回答ありがとうございます。
-Aitch

1

もちろん、横断的な懸念があるため、SRPの違反です。ただし、アクションを実行してロギングを構成するクラスを作成できます。

例:

class Logger {
   ActuallLogger logger;
   public Action ComposeLog(string msg, Action action) {
      return () => {
          logger.debug(msg);
          action();
      };
   }
}

2
ダウン投票。ロギングは、まさに横断的な関心事です。コード内のメソッド呼び出しの順序付けも同様です。それは、SRPの違反を主張するのに十分な理由ではありません。アプリケーションで特定のイベントの発生をログに記録することは、横断的な関心事ではありません。これらのメッセージが関心のあるユーザーに伝わる方法は確かに別の懸念事項であり、実装コードでこれを記述することはSRPの違反です。
ローランラリッツァ

「メソッド呼び出しのシーケンス」または機能構成は、横断的な関心事ではなく、実装の詳細です。作成した関数の役割は、アクションを含むログステートメントを作成することです。この関数の機能を説明するために「and」という言葉を使用する必要はありません。
ポールニコノビッチ

実装の詳細ではありません。コードの形状に大きな影響を与えます。
ローランラリッツァ

「この機能は何をするのか」という観点からSRPを見ているように、「この機能は何をするのか」という観点からSRPを見ていると思います。
ポールニコノビッチ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.