gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 01:29:01 30 4
gpt4 key购买 nike

读完“What’s your/a good limit for cyclomatic complexity?”后,我意识到我的许多同事对这个新的QA感到非常恼火。我们项目的政策:不再有 10 cyclomatic complexity每个功能。

含义:不超过10个'if'、'else'、'try'、'catch'等代码工作流分支语句。正确的。正如我在'Do you test private method?中解释的那样',这样的政策有很多好的副作用。

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

myLogger.info("A String");
myLogger.fine("A more complicated String");
...

当我们的系统的第一个版本上线时,我们遇到了巨大的内存问题,不是因为日志记录(它一度被关闭),而是因为日志参数(字符串),这些值总是被计算出来,然后传递给“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 在上面写“A String”时,我实际上指的是 aFunction(),aFunction() 返回一个 String,并调用一个复杂的方法来收集和计算要由记录器显示的所有类型的日志数据......或不(因此存在问题,以及使用条件日志记录的义务,因此人为增加“圈复杂度”的实际问题......)

我现在得到 ' variadic你们中的一些人提出了“功能”观点(谢谢约翰)。
注意:java6 中的快速测试表明我的 varargs function在调用之前会评估其参数,因此它不能应用于函数调用,但适用于“日志检索器对象”(或“函数包装器”),只有在需要时才会调用 toString() 。明白了。

我现在已经发布了我在这个主题上的经验。
我会把它留在那里直到下周二投票,然后我会选择你的答案之一。
再次感谢您的所有建议:)

最佳答案

对于当前的日志框架,这个问题没有实际意义

当前的日志框架(如 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 中,每个日志语句都会创建一个新的 StringBuilder,并在连接到字符串的每个对象上调用 toString() 方法。这些 toString() 方法可能会创建自己的 StringBuilder 实例,并调用其成员的 toString() 方法,等等,跨越一个可能很大的对象图。 (在 Java 5 之前,它的成本更高,因为使用了 StringBuffer,并且它的所有操作都是同步的。)

这可能成本相对较高,尤其是当日志语句位于某些执行频繁的代码路径中时。而且,如上所述,即使记录器由于日志级别太高而必然丢弃结果,也会发生昂贵的消息格式化。

这导致引入以下形式的保护语句:

  if (log.isDebugEnabled())
log.debug("Attempting connection of dongle " + d + " to widget " + w);

使用此保护,仅在必要时才执行参数 dw 的计算以及字符串连接。

简单、高效的日志记录解决方案

但是,如果记录器(或围绕所选日志记录包编写的包装器)采用格式化程序和格式化程序的参数,则可以延迟消息构造,直到确定将使用它为止,同时消除防护语句及其圈复杂度。

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() 调用,除非有必要!这有效地消除了导致 Guard 语句的性能影响。在 Java 中,一个小的缺点是对传递给记录器的任何基本类型参​​数进行自动装箱。

可以说,执行日志记录的代码比以往任何时候都更加干净,因为不整齐的字符串连接已经消失了。如果格式字符串被外部化(使用ResourceBundle),它会更干净,这也有助于软件的维护或本地化。

进一步增强

另请注意,在 Java 中,可以使用 MessageFormat 对象来代替“格式”String,它为您提供了额外的功能,例如选择格式更巧妙地处理基数。另一种选择是实现您自己的格式化功能,该功能调用您为“评估”定义的某些接口(interface),而不是基本的 toString() 方法。

关于language-agnostic - 具有最小圈复杂度的条件日志记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/105852/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com