if(x!=y) vs if(x==y)

23
我在Eclipse中运行了PMD插件,并且针对我的代码得到了一个高优先级警告,类似于下面展示的代码:
```java if (someCondition == true) { // ... } ```
 if(singleRequest !=null){
   // do my work
 }else{
   // do my other work
 }

PMD建议避免使用`if (x != y) ..; else ..;

错误的描述如下:

In an "if" expression with an "else" clause, avoid negation in
the test.  For example, rephrase:
if (x != y) diff(); else same();
as:
if (x == y) same(); else diff();
Most "if (x != y)" cases without an "else" are often return

但我仍然无法理解对我的代码产生的影响。如果有人能够给我提供一个示例指导,我将不胜感激。


4
这不就是插件对你的代码结构过于教条吗? - thatidiotguy
20
建议避免使用否定条件句。我认为在这种情况下这是荒谬的,因为“否定条件句”实际上会导致“正面情况”。你的代码很好。 - user166390
4
可读性并不总是问题所在......可读性标准的重要之处在于知道何时忽略它们的警告,因为实际上代码写成这样更易读。如果你认为使用否定词更有意义,那就写否定词。这种操作符存在的原因就在于此,否则就不会有"!="操作符。请记住,很多风格模块都是由于这些人对风格非常教条,他们经常代表他们的意见,这是任意的。 - hsanders
1
我经常看到这个建议用于C++编程,通常是因为==和!=运算符可以在类上被重载,而!=运算符通常只调用==运算符并否定结果,所以代码最终变成了两个跳转而不是一个(因此稍微慢一些)。但我不知道这如何适用于Java,因为你不能重载运算符。 - Darrel Hoffman
我自1971年开始编程,从未听说过所谓的“良好风格”。 - user207421
重要的是通过避免双重否定来提高代码可读性。这就像看门人告诉你只有当你不是“不到21岁”(或在你的情况下,“不是不是空值”,也就是“空值”)时才能进入。 - Marius Schulz
9个回答

33

13
PMD是一种工具。PMD基于启发式算法工作。有人决定采用这种启发式算法,即带有else语句的负面条件不是“好的风格”。
然而,在这种情况下,正如我在评论中所辩论的那样,“发布的代码就是写的代码”(特别是使用x!= null,但不仅限于此结构)。
这是因为我不看条件(除非它可以简化;例如,像Jim Kin所示的去除双重否定),而是看分支或“流程”的逻辑。
也就是说,我首先放置正面的分支。在这种情况下,我认为
if (x != null) {
  doValid         // positive branch
} else {
  doFallback
}

语义上等同于

if (isValid(x)) { // it looks like a "positive conditional" now
  doValid         // still positive branch
} else {
  doFallback
}

因此是正向的分支

当然,并非所有情况都具有如此“明显”的正向流,某些表达可能更容易用负面方式表达。在这些情况下,我将“反转”分支 - 与PMD建议的类似 - 通常会在块的顶部附上注释说明正向分支/流已被反转。

可能影响条件选择的另一个因素是“立即作用域退出”分支,例如:

if (x == null) {
  // return, break, or
  throw new Exception("oops!");
} else {
  // But in this case, the else is silly
  // and should be removed for clarity (IMOHO) which,
  // if done, avoids the PMD warning entirely
} 

这是我如何一贯(除了偶尔的例外)编写代码的方式:if (x != null) { .. }。利用可用的工具,并让它们为你服务。请参见Steven的答案,了解如何在此处配置PMD以获得更适合您的“口味”。


9

这是一个可读性问题。请考虑:

if ( x != y ) 
{
}
else  // "if x doesn't not equal y"
{
}

对比。

if ( x == y )
{
}
else  // "if x doesn't equal y"
{
}

后面的例子更容易识别。注意,我并不认为使用否定词有什么问题... 它可以更有意义,考虑一下。
if ( x != null )...

4
不是“可读性问题”,而是“PMD问题”。我一直使用if (isValid) { doValidWork } else { doFalbackWork }。只是恰巧在“isValid”条件语句中使用了!= - user166390
更容易识别的原因是什么?我没有发现这两种情况之间有任何区别。你的伪注释并没有给我留下深刻印象。任何东西都可以用错误的方式来表达。 - user207421

4

如果使用否定情况会导致双重否定,那么我不会选择这种方式,因为这可能会令人困惑。

例如:

if (!checkbox.disabled) {
    // checkbox is enabled
} 
else {
    // checkbox is disabled
}

1
是的,这使用确实令人困惑。它也与原始问题(OP)有一些微妙但重要的不同之处。 - user166390

3

谁阅读你的代码?你自己、编译器或许是讲师的助手,但绝不能是一个同事连“==”和“!=”都分不清的人吧?

我只能认为复杂表达式中否定形式往往不好。 (语境是:至少对于我来说。 我知道我在脑海中调试时会受到挫败:while(!expr&&!expr2 || expr3){}

ch = getch(); if (ch !='a') 这个模式可以轻松扩展为
if (ch !='a' || ch !='b'),虽然听起来语义上正确,但始终返回真值。

从性能角度考虑,最好将概率排序。

if (more_probable) {
      ....
      unconditional_jump_to_end_of_block;
} else {
   ...
}

选择这个选项应该能够提高性能,因为在更可能的分支中没有错误预测的惩罚。

if (p && p->next) 从性能角度来看会给出较差的结果。


1

这是一个关于代码可读性与代码组织之间平衡的案例。警告基本上是在暗示,对于阅读代码的人来说,导航否定的负面影响会令人困惑。

我的个人经验法则是,在if语句中,你应该测试你所期望的“正常”情况。考虑以下情况:

 if (x != y) {
   // do work here...
 } else {
   throw new IllegalArgumentException();
 }

在这种情况下,我会说重要的工作是在x != y的情况下完成的,所以这就是你应该测试的内容。这是因为我喜欢组织代码,让重要的工作先完成,然后再处理异常情况。

1

在 if 条件语句中,应避免使用 "不等于"。这是因为当其他人查看您的代码时,他们可能会忽略 !=,并对程序逻辑产生错误的结论。

对于您的情况,您可能需要交换 if 逻辑和 else 逻辑,并将 != 更改为 ==。


3
我相信任何明智的程序员都会仔细查看 != 与 == 的区别。问题更多地出现在组合逻辑表达式和语义上,比如 !disabled 与 enabled 或者 !notReady。 - Aki Suihkonen
1
我坚信, 人们对未来读者可能会和不会做什么有很多幻想。这是一个典型的例子。“那些神秘的程序员是谁?他们会把!=看作==而不是反过来吗?或者他们不能理解!disabled?(或者不能记住三年级的运算符优先级,作为另一个例子?)我从没见过这样的人。”上一次我遇到重大的编码风格错误的工作人员问题是在1982年。 - user207421

0

这并不是一个答案,但你可以通过尽早返回或失败并继续而不缩进来最小化总体复杂性并提高可读性:

if (something == null) {
    throw new IllegalArgumentException("something must not be null");
}

// continue here

0

这是因为“良好的风格”认为,如果可能的话测试应该是“积极的”,所以:

if (singleRequest == null){
   // do my other work
} else {
   // do my work
}

使用“正向”测试(即“等于”,而不是“不等于”)更容易阅读,最终更好的可读性会导致更少的错误。

编辑

这在测试如下的情况下尤其如此:

if (!str.equals("foo")) {

你很容易错过前面的!,但如果你将测试变成正向的,代码会更加简洁。

唯一需要进行负向测试的情况是没有else块时 - 这时候进行负向测试是不可避免的,除非你有一个空的true块,但这本身就被认为是一个风格问题。


2
为什么这是“好的风格”?(顺便感谢您使用引号 :-) 我一直将积极的工作放在第一个条件分支中。因此,我的代码在这方面是一致的独立于!===(例如,x != null -> isValid(x))。我不必想知道“哪个分支是积极的?”?我在我的代码中知道* - 第一个。 - user166390
这是一个好的编程风格,因为它 a) 保持一致性,b) 当我们在处理程序流程时,我们的小脑袋更容易理解正面测试。 - Bohemian
我认为必须保持一致的不是“条件”,而是“分支”(“正向流程”)。 - user166390
这只是在回避问题,而不是一个答案。 - user207421
@EJP 不,这真的是一个答案。我不明白为什么你会说这不是一个答案。基本上,人类比负面测试更容易解析正面测试。 - Bohemian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接