Keep if clauses side-effect free
Keep if clauses side-effect free / 保持 if 语句无副作用
Keep if clauses side-effect free. Avoid writing if clauses that have side effects: if (enqueueMessage(message)) { ... } The only function of an if statement is to test whether a condition is true. It’s not for executing code as a side-effect of the test.
保持 if 语句无副作用。避免编写带有副作用的 if 语句,例如:if (enqueueMessage(message)) { ... }。if 语句的唯一功能是测试条件是否为真,而不是为了在测试过程中产生副作用而执行代码。
One problem with using the return value directly, as in the above, is that the meaning of the returned value is unclear. Does enqueueMessage() return true if the message was enqueued or true if the queue is full? Make it explicit by using a variable:
boolean success = enqueueMessage(message); if (success) { ... }
The above code reads more like English: “If we were successful, …”
直接使用返回值(如上例所示)的一个问题是,返回值的含义不明确。enqueueMessage() 返回 true 是因为消息已入队,还是因为队列已满?通过使用变量来明确意图:
boolean success = enqueueMessage(message); if (success) { ... }
上述代码读起来更像英语:“如果我们成功了……”
Methods that don’t have side-effects are (we hope) named so that their return value is clear, such as isEmpty(). This isn’t only true of boolean-valued methods. This code isn’t very clear: if (flushQueue() == 0) { ... } whereas this one is:
int itemsFlushed = flushQueue(); if (itemsFlushed == 0) { ... }
没有副作用的方法(我们希望)命名清晰,使得返回值一目了然,例如 isEmpty()。这不仅适用于布尔值方法。这段代码不够清晰:if (flushQueue() == 0) { ... },而下面这段则清晰得多:
int itemsFlushed = flushQueue(); if (itemsFlushed == 0) { ... }
Another drawback of calling methods with side effects in if statements is that the entire call could be missed by a reader skimming the code. Compare the two examples with flushQueue() above. In the first the reader could mistake the call for a query that returns some queue attribute. The second more clearly has two parts: in the first an action is taken, and in the second a test is performed.
在 if 语句中调用带有副作用的方法的另一个缺点是,快速浏览代码的读者可能会忽略整个调用。比较上面 flushQueue() 的两个例子。在第一个例子中,读者可能会误以为该调用是一个返回队列属性的查询。第二个例子则清晰地分为两部分:第一部分执行操作,第二部分执行测试。
Consider this code I saw in production: if (!categorySeen.add(categoryID)) continue; I couldn’t figure where in the loop items were being added to the set. I was reading that line as: if (!categorySeen.contains(categoryID)) continue; because I expected the contents of an if statement to have no side effects. But even when I noticed the add() I couldn’t figure out what this did. Can you? (According to the Javadoc of Set the add() method “returns true if this set did not already contain the specified element”.)
考虑我在生产环境中看到的一段代码:if (!categorySeen.add(categoryID)) continue;。我无法弄清楚循环中是在哪里将项目添加到集合中的。我当时把那行代码读作:if (!categorySeen.contains(categoryID)) continue;,因为我预期 if 语句的内容不应有副作用。但即使我注意到了 add(),我也无法理解它的作用。你能理解吗?(根据 Set 的 Javadoc,add() 方法“如果此集合尚未包含指定元素,则返回 true”。)
And note the extra convoluted logic because of the continue (see Avoid continue). The rest of the code will run if the categoryID was not not not already seen: one not for the continue, one not for the !, and one not as part of the API’s description. What?! How about:
boolean isNewCategory = categorySeen.add(categoryID); if (isNewCategory) { ... }
还要注意由于 continue 导致的额外复杂逻辑(参见“避免使用 continue”)。如果 categoryID 没有被“没没没”看到,代码的其余部分才会运行:一个“没”对应 continue,一个“没”对应 !,还有一个“没”是 API 描述的一部分。什么?!试试这样:
boolean isNewCategory = categorySeen.add(categoryID); if (isNewCategory) { ... }
Here’s a dangerous combination of a method with side effects and abuse of short-circuit evaluation: if (queueNeedsFlushing() && flushQueue() == 0) { ... } The second call is particularly easy to miss. Short-circuit evaluation was intended to protect errors in evaluating a side-effect-free statement, such as: if (count > 0 && total/count >= MIN_AVERAGE) { ... } or: if (name != null && name.endsWith(".png")) { ... }
这是一个带有副作用的方法与滥用短路求值的危险组合:if (queueNeedsFlushing() && flushQueue() == 0) { ... }。第二个调用特别容易被忽略。短路求值旨在防止在评估无副作用语句时出现错误,例如:if (count > 0 && total/count >= MIN_AVERAGE) { ... } 或 if (name != null && name.endsWith(".png")) { ... }。
Don’t use the mechanism to avoid calling a method with side effects. That’s what if statements were invented for:
if (queueNeedsFlushing()) { int itemsFlushed = flushQueue(); if (itemsFlushed == 0) { ... } }
不要利用这种机制来避免调用带有副作用的方法。if 语句就是为此而发明的:
if (queueNeedsFlushing()) { int itemsFlushed = flushQueue(); if (itemsFlushed == 0) { ... } }
You’re doing yourself and future readers harm if you think that the terse version above is better than the three-line version here. Three lines is a small price to pay when you’re later having a hard time following the code because you keep missing important calls to methods.
如果你认为上面那种简洁的版本比这里的三行版本更好,那么你是在伤害自己和未来的读者。当你以后因为不断错过重要的方法调用而难以理解代码时,三行代码的代价微不足道。