web-dev-qa-db-ja.com

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

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

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

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

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

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

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

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

利点:-ログを記録せずにAを使用できます-テストAはログモックが不要であることを意味します-テストがより簡単です

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

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

19
Aitch

はい、ロギングは横断的な関心事であるため、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);
    }
}

特典には

  • ロギングなしでこれをテストできます-true単体テスト
  • ロギングのオン/オフを簡単に切り替えることができます-実行時でも
  • tenantStoreファイルを変更する必要なく、他の形式のログをログに置き換えることができます。
0
z0mbi3

SRPを真剣に受け止めすぎていると思います。コードが整頓されていて、ロギングがSRPの唯一の「違反」である場合、他のすべてのプログラマーの99%より上手くやっているので、背中をなでる必要があります。

SRPのポイントは、さまざまなことを行うコードがすべて混ざり合う恐ろしいスパゲッティコードを回避することです。ロギングと機能コードを混在させても、私には警報音は鳴りません。

61
grahamparks

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

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

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

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

また、実装では、メッセージの送信先のロガーがNullLoggerであるかどうかも認識しないでください。

それは言った。

横断的な懸念が速すぎるため、ログを一掃しません。実装コードで発生する特定のイベントを追跡するためにログを発行することは、実装コードに属します。

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

15

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

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

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

7
Oliver Weiler

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

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

これには1つの責任があります。それに渡されるロギング情報です。

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

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

これは重要な部分です:

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

そのように述べたとき、それは複雑に聞こえますが、このようにそれを見てください:コンポーネントDはone事をします:AとLを一緒にします。

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

コンポーネントDのonlyの責任は、Aが使用されたときにLに通知されることを確認することです。 AとLの実装はどちらも別の場所にあります。これは完全にSRPに準拠しており、OCPの適切な例であり、デコレータのごく一般的な使用方法です。

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

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

そうすれば、他のものに直接依存するものは何もないので、それらを簡単に交換できます。これにより、変更への適応が簡単になり、システムのパーツを簡単に模擬できるため、単体テストを実行できます。

5
anaximander

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

例:

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