对于当前的日志框架,这个问题没有实际意义
当前的日志框架,如 slf4j 或 log4j 2 在大多数情况下不需要保护语句。他们使用参数化的日志语句,以便可以无条件地记录事件,但只有在启用事件时才会发生消息格式化。消息构造由记录器根据需要执行,而不是由应用程序先发制人。
如果您必须使用古董日志库,您可以继续阅读以获取更多背景信息以及使用参数化消息改造旧库的方法。
守卫语句真的增加了复杂性吗?
考虑从圈复杂度计算中排除日志保护语句。
可以说,由于其可预测的形式,条件日志检查确实不会增加代码的复杂性。
不灵活的指标会使原本优秀的程序员变坏。当心!
假设您的计算复杂性的工具无法针对该程度进行定制,以下方法可能会提供一种解决方法。
需要条件记录
我假设你的保护语句被引入是因为你有这样的代码:
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 语句都会创建一个 new ,并在连接到字符串的每个对象上StringBuilder
调用该方法。反过来,toString()
这些方法可能会创建自己的实例,并调用其成员的方法,等等,跨越一个潜在的大型对象图。(在 Java 5 之前,它更加昂贵,因为使用过,并且它的所有操作都是同步的。)toString()
StringBuilder
toString()
StringBuffer
这可能相对昂贵,特别是如果日志语句位于某些执行量很大的代码路径中。而且,如上所述,即使记录器由于日志级别太高而必然丢弃结果,也会发生昂贵的消息格式化。
这导致了以下形式的保护语句的引入:
if (log.isDebugEnabled())
log.debug("Attempting connection of dongle " + d + " to widget " + w);
d
使用此保护,仅在必要时执行参数和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;
}
}
现在,除非必要,否则不会发生带有缓冲区分配的级联调用!toString()
这有效地消除了导致防护语句的性能损失。在 Java 中,一个小的惩罚是对您传递给记录器的任何原始类型参数进行自动装箱。
可以说,进行日志记录的代码比以往任何时候都更干净,因为不整洁的字符串连接已经消失了。如果格式字符串被外部化(使用 a ResourceBundle
),它会更干净,这也有助于软件的维护或本地化。
进一步的增强
另请注意,在 Java 中,MessageFormat
可以使用对象来代替“格式” String
,这为您提供了额外的功能,例如可以更巧妙地处理基数的选择格式。另一种选择是实现您自己的格式化功能,该功能调用您为“评估”定义的某些接口,而不是基本toString()
方法。