程序员应该使用布尔变量来“记录”他们的代码吗?

我正在读 McConell 的 代码完成,他讨论了使用布尔变量来记录代码:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) ||
(elementIndex == lastElementIndex)){
...
}

他建议:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
...
}

在我看来,这是合乎逻辑的、良好的实践,而且非常自我记录。然而,由于我几乎从来没有遇到过这种技术,所以我对于开始定期使用它感到犹豫不决; 也许仅仅因为它的罕见就会让人感到困惑。然而,我的经验还不是很丰富,所以我有兴趣听听程序员对这项技术的看法,我很好奇是否有人经常使用这项技术,或者在阅读代码时经常看到它。这是一个值得采用的约定/风格/技术吗?其他程序员会理解并欣赏它吗,还是会认为它很奇怪?

4084 次浏览

将一个过于嵌套和复杂的表达式拆分为分配给局部变量的简单子表达式,然后再组合在一起,这是一种相当普遍和流行的技术——完全独立于子表达式和/或整个表达式是布尔型还是其他类型。对于精心选择的名称,这种有品味的分解可以提高可读性,一个好的编译器应该不难生成与原始复杂表达式等价的代码。

一些本身没有“赋值”概念的语言,比如 Haskell,甚至引入了专门的结构来让你使用“赋一个子表达式的名称”技术(哈斯克尔的 where子句)——似乎预示着这种技术的流行!-)

只要有可能,我都尽力去做。当然,您正在使用“额外的一行”代码,但同时,您正在描述为什么要对两个值进行比较。

在您的示例中,我查看代码,然后问自己“好吧,为什么这个人看到的值小于0?”在第二种情况下,你清楚地告诉我,当这种情况发生时,一些过程已经完成了。第二个不用猜你的意图是什么。

对我来说最大的问题是当我看到像: DoSomeMethod(true);这样的方法时,为什么它会自动设置为 true?更容易读懂

bool deleteOnCompletion = true;


DoSomeMethod(deleteOnCompletion);

我使用过它,尽管通常将布尔逻辑包装成可重用的方法(如果从多个位置调用的话)。

它有助于提高可读性,当逻辑发生变化时,它只需要在一个地方进行变更。

其他人会理解它,也不会觉得奇怪(除了那些只写过千行函数的人)。

我认为,这取决于你/你的团队喜欢什么样的风格。“引入变量”重构可能是有用的,但有时并非如此:)

我不同意凯文之前的观点。他的例子,我想,可用于当引入的变量可以被修改时,但是只为一个静态布尔引入它是无用的,因为我们在方法声明中有参数名称,所以为什么要在代码中复制它?

例如:

void DoSomeMethod(boolean needDelete) { ... }


// useful
boolean deleteOnCompletion = true;
if ( someCondition ) {
deleteOnCompletion = false;
}
DoSomeMethod(deleteOnCompletion);


// useless
boolean shouldNotDelete = false;
DoSomeMethod(shouldNotDelete);

我认为出错的唯一原因是布尔片段没有一个有意义的名称,而且无论如何都选择了一个名称。

//No clue what the parts might mean.
if(price>0 && (customer.IsAlive || IsDay(Thursday)))


=>


first_condition = price>0
second_condition =customer.IsAlive || IsDay(Thursday)


//I'm still not enlightened.
if(first_condition && second_condition)

我之所以指出这一点,是因为制定诸如“对所有代码进行注释”、“对所有条件使用命名布尔值(超过3个部分)”之类的规则是很常见的,这些规则只能得到下列类型的语义为空的注释

i++; //increment i by adding 1 to i's previous value

这么做

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
...
}

你把 逻辑从你的大脑中移除,并把它放入代码中。现在程序 知道你的意思是什么。
无论什么时候你给它 姓名什么你给它 身体上的表示。它存在。
你可以操作和重用它。

您甚至可以将整个块定义为谓词:

bool ElementBlahBlah? (elementIndex, lastElementIndex);

并在该函数中做更多的事情(稍后)。

如果表达式很复杂,那么我要么将它移动到返回 bool的另一个函数,例如 isAnEveningInThePubAGoodIdea(dayOfWeek, sizeOfWorkLoad, amountOfSpareCash),要么重新考虑代码,这样就不需要这么复杂的表达式了。

请记住,这样你的计算超出了必要的范围。由于从代码中删除了条件,所以总是同时计算这两个条件(没有短路)。

因此:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) ||
(elementIndex == lastElementIndex)){
...
}

在转变成为:

if((elementIndex < 0) || (MAX_ELEMENTS < elementIndex) |
(elementIndex == lastElementIndex)){
...
}

在大多数情况下不是问题,但在某些情况下,它可能意味着更差的性能或其他问题,例如,在第二个表达式中,你假设第一个表达式失败了。

提供的样本:

finished = ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
repeatedEntry = (elementIndex == lastElementIndex);
if(finished || repeatedEntry){
...
}

也可以重写为使用方法,这样可以提高可读性并保留布尔逻辑(正如 Konrad 指出的) :

if (IsFinished(elementIndex) || IsRepeatedEntry(elementIndex, lastElementIndex)){
...
}


...


private bool IsFinished(int elementIndex) {
return ((elementIndex < 0) || (MAX_ELEMENTS < elementIndex));
}


private bool IsRepeatedEntry(int elementIndex, int lastElementIndex) {
return (elementIndex == lastElementIndex);
}

当然,这是要付出代价的,这就是两种额外的方法。如果您经常这样做,可能会使您的代码更具可读性,但是您的类不那么透明。不过话说回来,您也可以将额外的方法移动到 helper 类中。

我认为创建函数/方法比创建临时变量更好。这种方法也因为方法变得更短而提高了可读性。Martin Fowler 的《重构》一书对于提高代码质量有很好的建议。与您的特定示例相关的重构被称为“用查询替换临时值”和“提取方法”。

就我个人而言,我认为这是一个很好的实践。它对代码执行的影响很小,但是如果使用得当,它所能提供的清晰度在以后维护代码时是非常宝贵的。

根据我的经验,我经常回到一些旧的剧本,并想知道“那时候我到底在想什么?”?'.例如:

Math.p = function Math_p(a) {
var r = 1, b = [], m = Math;
a = m.js.copy(arguments);
while (a.length) {
b = b.concat(a.shift());
}
while (b.length) {
r *= b.shift();
}
return r;
};

这不像直觉那么准确:

/**
* An extension to the Math object that accepts Arrays or Numbers
* as an argument and returns the product of all numbers.
* @param(Array) a A Number or an Array of numbers.
* @return(Number) Returns the product of all numbers.
*/
Math.product = function Math_product(a) {
var product = 1, numbers = [];
a = argumentsToArray(arguments);
while (a.length) {
numbers = numbers.concat(a.shift());
}
while (numbers.length) {
product *= numbers.shift();
}
return product;
};

我很少创建单独的变量。当测试变得复杂时,我所做的就是嵌套 IF 并添加注释。喜欢

boolean processElement=false;
if (elementIndex < 0) // Do we have a valid element?
{
processElement=true;
}
else if (elementIndex==lastElementIndex) // Is it the one we want?
{
processElement=true;
}
if (processElement)
...

这种技术公认的缺陷是,下一个程序员可能会改变逻辑,但不会费心更新注释。我想这是一个普遍的问题,但是我已经有很多次看到一个评论说“验证客户 ID”,下一行是检查零件号码或者其他类似的东西,我想知道客户 ID 是从哪里来的。

如果该方法需要成功通知: (c # 中的例子) 我喜欢用

bool success = false;

开始的时候,这个代码是个错误,直到我把它改成:

success = true;

最后:

return success;