我正在使用SonarLint向我显示以下行中的问题。
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
旁注:包含此行的方法可能会经常被调用。
该问题的描述是
"前提条件"和记录参数不应该要求评估 (鱿鱼:S2629)
将需要进一步评估的消息参数传递给Guava com.google.common.base.Preconditions检查可以带来效果 罚款。这是因为他们是否需要,每个论点 必须在实际调用方法之前解决。
类似地,将连接的字符串传递给日志记录方法也可以 由于串联将导致不必要的性能损失 每次调用该方法时执行,无论是否为日志 级别足够低以显示消息。
相反,您应该构造代码以传递静态或预先计算 值进入前置条件条件检查和记录调用。
具体来说,应该使用内置字符串格式代替 字符串连接,如果消息是方法的结果 打电话,然后应该跳过前提条件altoghether,和 相反的例外应该有条件地抛出。
不合规代码示例
logger.log(Level.DEBUG, "Something went wrong: " + message); // Noncompliant; string concatenation performed even when log level too high to show DEBUG messages LOG.error("Unable to open file " + csvPath, e); // Noncompliant Preconditions.checkState(a > 0, "Arg must be positive, but got " + a); // Noncompliant. String concatenation performed even when a > 0 Preconditions.checkState(condition, formatMessage()); //Noncompliant. formatMessage() invoked regardless of condition Preconditions.checkState(condition, "message: %s", formatMessage()); // Noncompliant
合规解决方案
logger.log(Level.SEVERE, "Something went wrong: %s", message); // String formatting only applied if needed logger.log(Level.SEVERE, () -> "Something went wrong: " + message); //since Java 8, we can use Supplier , which will be evaluated lazily LOG.error("Unable to open file {}", csvPath, e); if (LOG.isDebugEnabled() { LOG.debug("Unable to open file " + csvPath, e); // this is compliant, because it will not evaluate if log level is above debug. } Preconditions.checkState(arg > 0, "Arg must be positive, but got %d", a); // String formatting only applied if needed if (!condition) { throw new IllegalStateException(formatMessage()); // formatMessage() only invoked conditionally } if (!condition) { throw new IllegalStateException("message: " + formatMessage()); }
我不能100%确定我是否理解这一点。那为什么这真的是一个问题。特别是关于使用字符串连接时性能的部分。因为我经常读到字符串连接比格式化更快。
编辑:也许有人可以解释我之间的区别
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
和
LOGGEr.debug("Comparing objects: {} and {}",object1, object2);
在后台。因为我认为在传递给方法之前会创建String。对?所以对我来说没有区别。但显然我错了,因为SonarLint抱怨它
答案 0 :(得分:8)
我相信你有答案。
预先计算连接条件检查。因此,如果您有条件地将日志框架调用10K次并且所有日志框架的评估结果为false,那么您将无缘无故地连接10K次。
同时检查this topic。并查看Icaro的回答。
也看看StringBuilder。
答案 1 :(得分:5)
考虑以下日志记录声明:
LOGGER.debug("Comparing objects: " + object1 + " and " + object2);
这是什么'调试'?
这是日志记录语句的级别,而不是LOGGER的级别。 看,有两个级别:
a)其中一个日志记录语句(在此处调试):
"Comparing objects: " + object1 + " and " + object2
b)一个是LOGGER的等级。那么,LOGGER对象的级别是什么: 这也必须在代码或某些xml中定义,否则它需要从它的祖先开始。
现在我为什么要说这一切?
现在,当且仅当以下情况下,将打印日志记录语句(或更多技术术语发送到其'appender'):
Level of logging statement >= Level of LOGGER defined/obtained from somewhere in the code
Level的可能值可以是
DEBUG < INFO < WARN < ERROR
(根据日志框架可能会有更多内容)
现在让我们回答问题:
"Comparing objects: " + object1 + " and " + object2
即使我们发现上面解释的“级别规则”失败,也会始终导致字符串的创建。
然而,
LOGGER.debug("Comparing objects: {} and {}",object1, object2);
如果“上面说明的级别规则”满足,只会导致字符串形成。
哪个更聪明?
咨询url。
答案 2 :(得分:3)
字符串连接意味着 LOGGER.info(&#34;程序从&#34; + new Date()开始);
内置格式化记录器意味着 LOGGER.info(&#34;程序从{}&#34;开始,新的Date());
非常好的文章,以了解差异 http://dba-presents.com/index.php/jvm/java/120-use-the-built-in-formatting-to-construct-this-argument
答案 3 :(得分:1)
首先让我们了解问题,然后讨论解决方案。
我们可以简化一下,假设以下示例
LOGGER.debug("User name is " + userName + " and his email is " + email );
上面的日志消息字符串由4部分组成
并且将需要构造3个String串联。
现在,让我们转到此日志记录语句的问题所在。
假设我们的日志记录级别为OFF
,这意味着我们现在对日志记录不感兴趣。
我们可以想象字符串连接(慢速操作)将被始终应用并且不会考虑日志记录级别。
哇,在理解了性能问题之后,我们来讨论最佳实践。
解决方案1(并非最佳)
除了使用字符串串联,我们还可以使用String Builder
StringBuilder loggingMsgStringBuilder = new StringBuilder();
loggingMsgStringBuilder.append("User name is ");
loggingMsgStringBuilder.append(userName);
loggingMsgStringBuilder.append(" and his email is ");
loggingMsgStringBuilder.append(email );
LOGGER.debug(loggingMsgStringBuilder.toString());
解决方案2(最佳)
在检查调试级别之前,我们不需要构造日志记录消息。
因此,我们可以将日志消息格式和所有部分作为参数传递给LOGGING引擎,然后将字符串连接操作委托给它,然后根据记录级别,引擎将决定是否进行串联。
因此,建议使用参数化日志记录作为以下示例
LOGGER.debug("User name is {} and his email is {}", userName, email);