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