【问题标题】:Should logging reside within a class who's primary purpose is not logging?日志记录应该驻留在一个主要目的不是日志记录的类中吗?
【发布时间】:2009-11-25 17:33:59
【问题描述】:

这更像是一个理论问题。日志记录应该驻留在一个主要目的不是日志记录的类中吗?

这里有一个简单的界面,可以用于对数字进行计算的任何东西。

public interface ICalculation { 
    public int calculate(int number); 
}

这是 ICalculation 接口的一个实现,它执行计算并进行一些日志记录。我相信这是一种非常务实的做法。除了构造函数接受我们通常不希望在计算域中看到的东西之外,内联日志记录可以说是非侵入性的。

public class ReallyIntenseCalculation : ICalculation {
    private readonly ILogger log;

    public ReallyIntenseCalculation() : this(new DefaultLogger()) {
    }

    public ReallyIntenseCalculation(ILogger log) {
        this.log = log;
        log.Debug("Instantiated a ReallyIntenseCalculation.");
    }

    public int calculate(int number) {
        log.Debug("Some debug logging.")
        var answer = DoTheDirtyWork(number);
        log.Info(number + " resulted in " + answer);
        return answer;
    }

    private int DoTheDirtyWork(int number) {
        // crazy math happens here
        log.Debug("A little bit of granular logging sprinkled in here.");
    }
}

从ReallyIntenseCalculation 中删除所有日志记录代码后,该代码现在似乎具有明确的单一职责

public class ReallyIntenseCalculation : ICalculation {

    public int calculate(int number) {
        return DoTheDirtyWork(number);
    }

    private int DoTheDirtyWork(int number) {
        // crazy math happens here
    }
}

好的,所以我们删除了真正强烈计算的记录其内部的能力。我们怎样才能找到一种方法来外部化该功能。输入装饰器模式。

通过创建一个装饰 ICalculation 的类,我们可以将日志记录重新添加到组合中,但这样做会损害在真正IntenseCalculation 的私有方法中发生的一些更精细的日志记录。

public class CalculationLoggingDecorator : ICalculation {
    private readonly ICalculation calculation;
    private readonly ILogger log;

    public CalculationLoggingDecorator(ICalculation calculation, ILogger log) {
        this.calculation = calculation;
        this.log = log;
        log.Debug("Instantiated a CalculationLoggingDecorator using " + calculation.ToString());
    }

    public int calculate(int number) {
        log.Debug("Some debug logging.")
        var answer = calculation.calculate(number);
        log.Info(number + " resulted in " + answer);
    }
}

拥有日志装饰器还有哪些其他可能的优点和缺点?

【问题讨论】:

    标签: logging decorator single-responsibility-principle decoupling solid-principles


    【解决方案1】:

    我认为这是值得商榷的。可以证明日志记录是该单一职责的一部分。

    也就是说,我认为您正在考虑cross-cutting concerns,这是aspect-oriented programming 处理的问题。事实上,日志代码是 AOP 的典型示例。您可能需要考虑像 aspect# 这样的 AOP 框架。

    这样做的好处当然是可分解、重用和关注点分离。

    【讨论】:

      【解决方案2】:

      我同意 Jason 的观点,这更像是一个横切关注点。

      一个函数实际上应该只做一个函数,因为这会使代码更清晰,也更容易测试。例如,如果我的单元测试因日志文件已满而失败,那会令人困惑,因为我没有在我的方法中测试日志记录。

      AOP 是这里的最佳选择,而对于 .NET,PostSharp 可能是最佳选择。

      如果 AOP 不是一个好的选择,那么您可能想要使用 DI,并且您可以注入具有日志记录的类的版本来测试流程,但是如果您要这样做,那么您应该确保注入的类只是进行日志记录,然后调用没有日志记录的相同函数,因此您在类周围放置了一个包装器。

      public class ReallyIntenseCalculation : ICalculation {
      
          public int calculate(int number) {
              return DoTheDirtyWork(number);
          }
      
          private int DoTheDirtyWork(int number) {
              // crazy math happens here
          }
      }
      
      public class CalculationLoggingDecorator : ICalculation {
          ICalculation calculation;
          ILogger log;
          public CalculationLogging() {
              this.calculation = new ReallyIntenseCalculation() ;
              this.log = SomeLogger(...);
              log.Debug("Initialized a CalculationLoggingDecorator using " + calculation.ToString());
          }
      
          public int calculate(int number) {
              log.Debug("Some debug logging.")
              var answer = calculation.calculate(number);
              log.Info(number + " resulted in " + answer);
          }
      }
      

      这类似于您的装饰器,但是当您将日志版本换成非日志版本时,您已经删除了所有多余的代码,并且通过测试日志版本,您可以确保正在使用 ReallyIntenseCalculation 并且方法只定义一次。

      这是更多的工作,AOP 更可取,但 DI 可能是替代方案。

      更新:基于评论。

      如果您有多个扩展此接口的类,那么您可能会有大量的类,但请为此设计。

      AOP 将是最好的方法,但与 DI 相比,该概念对某些公司来说很难推销。

      您最终可能会为每个实现创建两个类,但您的日志信息仍会从这些其他类中删除,每个类都通过 DI 注入,因此您可以拥有两个 app.config 文件,一个包含所有日志开设课程和生产课程,以简化您的生活。第二类只是有日志记录和设置信息,所以我相信这不是太多额外的工作,但你已经失去了日志记录的精细粒度。

      【讨论】:

      • 如果我要引入更多实现 ICalculations 的类,例如,ReallySimpleCalculation 和 AbsurdlyComplexCalculation。上面的代码不会容易爆类吗?
      【解决方案3】:

      您有多少个记录器?我会让计算器的构造函数的实现私下实例化一个记录器实例,使用 Logger 单例或 Logger 工厂方法(以便记录或不记录不是计算器公共 API 的一部分,而是计算器实现的一部分)。

      【讨论】:

      • 如果它是实现的一部分,那么它应该是一个构造函数参数。否则,查看 API 的任何人都意味着如何知道以及如果用户不想使用不同的记录器怎么办。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-06
      • 1970-01-01
      相关资源
      最近更新 更多