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

时间:2010-03-19 14:46:10

标签: boolean conventions self-documenting

我正在阅读McConell的代码完成,他讨论了使用布尔变量来记录代码。例如,而不是:

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

他建议:

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

这使我感到逻辑,良好的做法和非常自我记录。但是,我对于经常使用这种技术犹豫不决,因为我几乎从未遇到过这种技术;也许只是因为稀有而令人困惑。然而,我的经验还不是很大,所以我有兴趣听听程序员对这种技术的看法,我很想知道是否有人经常使用这种技术或者在阅读代码时经常看到它。这是一个值得采用的约定/风格/技术吗?其他程序员会理解并欣赏它,还是认为它很奇怪?

14 个答案:

答案 0 :(得分:54)

将一个过于嵌套和复杂的表达式拆分为分配给局部变量的简单子表达式,然后重新组合在一起,这是一种非常常见和流行的技术 - 完全独立于子表达式和/或整体表达式是布尔值或几乎任何其他类型。通过精心挑选的名称,这种有品味的分解可以提高可读性,并且良好的编译器应该可以轻松生成与原始复杂表达式相同的代码。

一些没有“赋值”概念的语言,比如Haskell,甚至引入了专门的结构来让你使用“给一个子表达式命名”技术(where子句Haskell) - 似乎对这种技术有一定的普及性! - )

答案 1 :(得分:16)

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

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

其他人会理解它并且不会觉得奇怪(除了那些只编写千行函数的人,就是这样)。

答案 2 :(得分:9)

我尝试尽可能地做到这一点。当然,您正在使用“额外的”代码,但与此同时,您正在描述为什么要对两个值进行比较。

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

对我而言最重要的是当我看到一个方法时:DoSomeMethod(true);为什么它会自动设置为true?它更具可读性

bool deleteOnCompletion = true;

DoSomeMethod(deleteOnCompletion);

答案 3 :(得分:5)

提供的样本:

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);
}

当然,这是一个代价,这是两种额外的方法。如果你做了很多,它可能会使你的代码更具可读性,但你的类不那么透明。但话又说回来,您还可以将额外的方法移动到辅助类中。

答案 4 :(得分:3)

我唯一能看到错误的方法是布尔片段没有合理的名称,无论如何都会选择名称。

//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个部分的所有if-criteria使用命名的布尔值”这样的规则是常见的,只是为了获得语义上没有以下排序的注释< / p>

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

答案 5 :(得分:2)

通过这样做

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

从大脑中删除逻辑并将其放入代码中。现在程序知道你的意思 每当您命名某事时,您就会给它物理表示。它存在。
您可以操纵和重复使用它。

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

bool ElementBlahBlah? (elementIndex, lastElementIndex);

并在该功能中做更多事情(稍后)。

答案 6 :(得分:2)

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

答案 7 :(得分:2)

我认为最好创建函数/方法而不是临时变量。这种方式可读性也因为方法变短而增加。 Martin Fowler的书Refactoring对提高代码质量提出了很好的建议。与您的特定示例相关的重构称为“使用查询替换Temp”和“提取方法”。

答案 8 :(得分:2)

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

答案 9 :(得分:1)

请记住,这种计算方式超出了必要的范围。由于从代码中取出条件,您总是计算它们(没有短路)。

那样:

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

转化后:

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

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

答案 10 :(得分:1)

如果方法需要成功通知:( c#中的示例)

我想使用

bool success = false;

开始。在我将其更改为:

之前,代码是一个错误
success = true;

然后在最后:

return success;

答案 11 :(得分:0)

我认为,这取决于您/您的团队喜欢什么样的风格。 “引入变量”重构可能很有用,但有时不会:)

我在上一篇文章中应该不同意凯文。我想,他的例子可用,以防万一,当引入变量可以改变时,但只引入一个静态布尔值是没用的,因为我们在方法声明中有参数名称,所以为什么要在代码中复制它?

例如:

void DoSomeMethod(boolean needDelete) { ... }

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

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

答案 12 :(得分:0)

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

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;
};

答案 13 :(得分:0)

我很少创建单独的变量。当测试变得复杂时,我做的是嵌套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进来了。