Language agnostic 具有最小圈复杂度的条件日志记录

Language agnostic 具有最小圈复杂度的条件日志记录,language-agnostic,logging,coding-style,cyclomatic-complexity,Language Agnostic,Logging,Coding Style,Cyclomatic Complexity,在阅读了“”之后,我意识到我的许多同事对我们项目的新政策非常恼火:每个功能不超过10个 含义:不超过10个“if”、“else”、“try”、“catch”和其他代码工作流分支语句。正确的。正如我在“”中所解释的,这样的政策有许多好的副作用 但是:在我们(200人-7年)项目开始时,我们很高兴地记录日志(不,我们不能轻易地将其委托给某种“日志”方法) 当我们的系统的第一个版本上线时,我们遇到了巨大的内存问题,这不是因为日志记录(在某一点上被关闭),而是因为日志参数(字符串),这些参数总是经过计算

在阅读了“”之后,我意识到我的许多同事对我们项目的新政策非常恼火:每个功能不超过10个

含义:不超过10个“if”、“else”、“try”、“catch”和其他代码工作流分支语句。正确的。正如我在“”中所解释的,这样的政策有许多好的副作用

但是:在我们(200人-7年)项目开始时,我们很高兴地记录日志(不,我们不能轻易地将其委托给某种“日志”方法)

当我们的系统的第一个版本上线时,我们遇到了巨大的内存问题,这不是因为日志记录(在某一点上被关闭),而是因为日志参数(字符串),这些参数总是经过计算,然后传递给“info()”或“fine()”函数,结果发现日志记录级别为“off”,而且没有发生伐木事件

所以QA回来敦促我们的程序员做条件日志记录。总是

if(myLogger.isLoggable(Level.INFO) { myLogger.info("A String");
if(myLogger.isLoggable(Level.FINE) { myLogger.fine("A more complicated String");
...
但是现在,有了“不能移动”10圈复杂度级别的每个函数限制,他们认为他们在函数中放入的各种日志被认为是一种负担,因为每个“if(isLoggable())”都算作+1圈复杂度

所以如果一个函数有8个“if”,“else”等等,在一个紧密耦合的不容易共享的算法中,还有3个关键的日志操作。。。即使条件日志可能不是函数复杂性的一部分,它们也会突破限制

您将如何解决这种情况?
在我的项目中,我看到了一些有趣的编码演变(由于“冲突”),但我只想先了解一下您的想法


谢谢您的回答。
我必须坚持,这个问题与“格式化”无关,而与“参数求值”有关(在调用一个什么都不起作用的方法之前,求值可能非常昂贵)
所以当a写上面的“字符串”时,我实际上是指函数(),函数()返回一个字符串,是对一个复杂方法的调用,该方法收集并计算记录器显示的所有类型的日志数据。。。或者不是(因此问题,以及使用条件日志记录的义务,因此实际问题是人为增加“圈复杂度”…)

现在,我得到了你们中一些人提出的“功能”观点(谢谢约翰)。
注意:java6中的一个快速测试显示my在被调用之前确实计算了它的参数,因此它不能应用于函数调用,而是应用于“Log retriever object”(或“function wrapper”),只有在需要时才会对其调用toString()。明白了

我现在已经发布了我在这个主题上的经验。
我将把它保留到下周二进行投票,然后我将从您的答案中选择一个。

再次,感谢您的所有建议:在C或C++中,

< P> >我使用预处理器代替条件日志的if语句。< /p> 将日志级别传递给记录器,并让它决定是否写日志语句:

//if(myLogger.isLoggable(Level.INFO) {myLogger.info("A String");
myLogger.info(Level.INFO,"A String");
更新:啊,我看到您希望有条件地创建日志字符串,而不使用条件语句。大概是在运行时而不是编译时

我只想说,我们解决这个问题的方法是将格式化代码放在logger类中,这样格式化只在级别通过时发生。非常类似于内置的sprintf。例如:

myLogger.info(Level.INFO,"A String %d",some_number);   
这应该符合您的标准。

对于当前的日志框架,这个问题没有实际意义 当前的日志框架,如slf4j或log4j 2,在大多数情况下不需要guard语句。它们使用参数化日志语句,以便无条件记录事件,但只有在启用事件时才会进行消息格式化。消息构造由记录器根据需要执行,而不是由应用程序先发制人地执行

如果您必须使用一个古老的日志库,您可以继续阅读以获得更多的背景信息,以及一种使用参数化消息改进旧库的方法

guard语句真的增加了复杂性吗? 考虑从圈复杂度计算中排除日志保护语句

可以说,由于其可预测的形式,条件日志检查实际上不会增加代码的复杂性

不灵活的度量标准会使原本优秀的程序员变得糟糕。小心

假设您的计算复杂性的工具无法定制到那种程度,下面的方法可能会提供一种解决方法

对条件日志记录的需要 我假设引入guard语句是因为您有这样的代码:

private static final Logger log = Logger.getLogger(MyClass.class);

Connection connect(Widget w, Dongle d, Dongle alt) 
  throws ConnectionException
{
  log.debug("Attempting connection of dongle " + d + " to widget " + w);
  Connection c;
  try {
    c = w.connect(d);
  } catch(ConnectionException ex) {
    log.warn("Connection failed; attempting alternate dongle " + d, ex);
    c = w.connect(alt);
  }
  log.debug("Connection succeeded: " + c);
  return c;
}
在Java中,每个log语句都创建一个新的
StringBuilder
,并对连接到字符串的每个对象调用
toString()
方法。这些
toString()
方法反过来可能会创建自己的
StringBuilder
实例,并调用其成员的
toString()
方法,等等,跨越一个潜在的大型对象图。(在Java5之前,它甚至更昂贵,因为使用了
StringBuffer
,并且它的所有操作都是同步的。)

这可能会相对昂贵,尤其是当log语句位于一些执行频繁的代码路径中时。而且,如上所述,即使日志记录器由于日志级别太高而必须放弃结果,也会出现昂贵的消息格式设置

这导致采用以下形式的保护声明:

  if (log.isDebugEnabled())
    log.debug("Attempting connection of dongle " + d + " to widget " + w);
使用此保护,参数
d
w
的求值以及字符串连接仅在必要时执行

一种简单、高效的日志记录解决方案 但是,如果记录器(或围绕所选日志包编写的包装器)采用格式化程序和格式化程序的参数,则消息构造可能会延迟,直到确定将使用它,同时消除保护语句
  if (log.isDebugEnabled())
    log.debug("Attempting connection of dongle " + d + " to widget " + w);
public final class FormatLogger
{

  private final Logger log;

  public FormatLogger(Logger log)
  {
    this.log = log;
  }

  public void debug(String formatter, Object... args)
  {
    log(Level.DEBUG, formatter, args);
  }

  … &c. for info, warn; also add overloads to log an exception …

  public void log(Level level, String formatter, Object... args)
  {
    if (log.isEnabled(level)) {
      /* 
       * Only now is the message constructed, and each "arg"
       * evaluated by having its toString() method invoked.
       */
      log.log(level, String.format(formatter, args));
    }
  }

}

class MyClass 
{

  private static final FormatLogger log = 
     new FormatLogger(Logger.getLogger(MyClass.class));

  Connection connect(Widget w, Dongle d, Dongle alt) 
    throws ConnectionException
  {
    log.debug("Attempting connection of dongle %s to widget %s.", d, w);
    Connection c;
    try {
      c = w.connect(d);
    } catch(ConnectionException ex) {
      log.warn("Connection failed; attempting alternate dongle %s.", d);
      c = w.connect(alt);
    }
    log.debug("Connection succeeded: %s", c);
    return c;
  }

}
LOGGER(LEVEL_INFO) << "A String";
log.info ("a = %s, b = %s", a, b)
public class MainClass {
    private class LazyGetEverything { 
        @Override
        public String toString() { 
            return getEverything().toString(); 
        }
    }

    private Object getEverything() {
        /* returns what you want to .toString() in the inner class */
    }

    public void logEverything() {
        log.info(new LazyGetEverything());
    }
}
public void Example()
{
  if(myLogger.isLoggable(Level.INFO))
      myLogger.info("A String");
  if(myLogger.isLoggable(Level.FINE))
      myLogger.fine("A more complicated String");
  // +1 for each test and log message
}
public void Example()
{
   _LogInfo();
   _LogFine();
   // +0 for each test and log message
}

private void _LogInfo()
{
   if(!myLogger.isLoggable(Level.INFO))
      return;

   // Do your complex argument calculations/evaluations only when needed.
}

private void _LogFine(){ /* Ditto ... */ }
Logger logger = ...
logger.log(Level.DEBUG,"The foo is {0} and the bar is {1}",new Object[]{foo, bar});
void debugUtil(String s, Object… args) {
   if (LOG.isDebugEnabled())
       LOG.debug(s, args);
   }
);
debugUtil(“We got a %s”, new Object() {
       @Override String toString() { 
       // only evaluated if the debug statement is executed
           return expensiveCallToGetSomeValue().toString;
       }
    }
);