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


79

我正在阅读McConell的Code Complete,他讨论了使用布尔变量来记录您的代码。例如,代替:

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

他建议:

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

这让我印象深刻,因为这是合乎逻辑的,良好的做法,并且非常有据可查。但是,由于我几乎从未遇到过这种方法,因此我不愿意定期开始使用这种技术。也许仅仅因为它是稀有的就令人困惑。但是,我的经验还不是很丰富,所以我很想听听程序员对此技术的看法,并且我很想知道是否有人定期使用此技术或在阅读代码时是否经常看到它。这是值得采用的惯例/风格/技术吗?其他程序员会理解和欣赏它,还是认为它很奇怪?


@Paul R-哎呀,谢谢。我从自我记录开始,意识到没有标签,因此尝试将其更改为已经存在的自我记录。:)
弗罗迪(Froadie)2010年

不仅仅是对测试中发生的事情进行评论更好吗?
JS 2010年

3
在适当的时候,我也会尝试执行此操作,特别是如果我必须在方法的不同部分中检查条件时。它也更具描述性。
geffchang 2010年

“这使我感到合乎逻辑,是一种良好的做法,而且非常有据可查。” 仅基于您的经验,其他初次接触它的程序员很容易理解它。

2
为什么这个问题仍然存在?
orangepips 2011年

Answers:


55

将过于嵌套和复杂的表达式拆分为分配给局部变量的更简单的子表达式,然后重新组合在一起,是一种非常普遍且流行的技术-完全独立于子表达式和/或整体表达式是布尔型还是布尔型几乎任何其他类型。使用精心选择的名称,这种高雅的分解可以提高可读性,并且好的编译器在生成与原始复杂表达式等效的代码时应该没有问题。

某些本身没有“赋值”概念的语言,例如Haskell,甚至引入了专门的结构以使您使用“给子表达式命名”(whereHaskell中的子句)-似乎在说一些该技术的普及!-)


6
如果它更简单易读,我说那是双赢的明显案例:)
djcouchycouch 2010年

我同意。在很多情况下,您可能只想简短评论一下,但是我看不出这有什么实际影响。
Max E.

16

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

它有助于提高可读性,并且当逻辑更改时,只需要在一个地方更改即可。

其他人会理解它,不会发现它很奇怪(就是那些只写过千行函数的人除外)。


感谢你的回答!关于可重用的方法,还有另一个(不相关的)正当理由要被排除在外...所以我想我的问题确实是,当除了可读性之外没有其他理由时,是否应该排除一次布尔型表达式。(当然这是一个足够大的理由:))感谢您指出这一点。
弗罗迪2010年

+1用于减少逻辑更改时必要的代码更改,并取笑了千行功能的程序员。
Jeffrey L Whitledge,2010年

9

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

在您的示例中,我看一下代码,并问自己“好吧,为什么看到该值的人小于0?” 在第二个中,您清楚地告诉我,某些过程已经完成。在第二个中没有猜到您的意图是什么。

对我来说,最重要的是,当我看到类似这样的方法时: DoSomeMethod(true); 为什么它会自动设置为true?它更具可读性,例如

bool deleteOnCompletion = true;

DoSomeMethod(deleteOnCompletion);

7
正是由于这个原因,我不喜欢布尔参数。您最终会收到类似“ createOrder(true,false,true,true,false)”的调用,这意味着什么?我更喜欢使用枚举,所以您说的是“ createOrder(Source.MAIL_ORDER,BackOrder.NO,CustomOrder.CUSTOM,PayType.CREDIT)”。
杰伊,2010年

但是,如果您遵循凯文(Kevin)的榜样,那就等于您的榜样。变量可以采用2个值还是大于2个值有什么区别?
Mark Ruzon 2010年

2
使用Jay's,您可以拥有在某些情况下绝对清晰的优势。例如,在使用PayType。如果它是布尔值,则该参数可能名为isPayTypeCredit。你不知道什么是替代品。使用枚举,您可以清楚地看到PayType的选项:信用卡,支票,现金,然后选择正确的选项。
kemiller2002's

++枚举也不允许空分配,因此值控制从字面上看是完整的,并且枚举的自动文档位于最上面
Hardryv 2010年

Objective-C和Smalltalk确实解决了这个问题,在Objective-C中:[Object createOrderWithSource:YES backOrder:NO custom:YES type:kCreditCard];
Grant Paul

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

当然,它是有代价的,这是另外两种方法。如果您经常这样做,可能会使您的代码更具可读性,但类的透明度却降低了。但是话又说回来,您也可以将额外的方法移到辅助类中。


如果您在C#中遇到了很多代码噪声,那么您还可以利用局部类并将噪声移至局部,并且如果人们对IsFinished正在检查的内容感兴趣,则很容易跳转。
克里斯·马里西奇

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使用命名布尔值”之类的规则只是为了获得语义上不属于以下种类的注释,这是司空见惯的

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

2
您对示例中的条件进行了错误分组,不是吗?“ &&”的绑定比“ ||”的绑定更紧密 在大多数使用它们的语言中(shell脚本是一个例外)。
乔纳森·勒夫勒

Parens添加了。因此,这将是防止拆分为命名变量的另一种方式,它为以非显而易见的方式更改分组提供了机会。
马修·马丁(MartinMartin)2010年

2

通过做这个

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

您从大脑中删除了逻辑,并将其放入代码中。现在程序知道您的意思了。
每当您命名某物时,您都可以对其进行物理表示。它存在。
您可以操纵和重用它。

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

bool ElementBlahBlah? (elementIndex, lastElementIndex);

并在该函数中执行更多操作(稍后)。


更重要的是,下一个查看代码的开发人员也将知道您的意思!这是很好的做法,我一直都这样做。
克里斯·桑顿

1
而且,下一个开发人员可能通常是您自己,几个月(甚至几周)后再次查看代码,并很高兴自己编写了完整的文档。
2014年

2

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


2

请记住,这样您可以进行更多的计算。由于要从代码中取出条件,因此您总是要对它们进行计算(无短路)。

以便:

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

转换后变为:

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

在大多数情况下这不是问题,但在某些情况下仍可能意味着性能下降或其他问题,例如,在第二个表达式中,您假定第一个表达式已失败。


是的-当我回到我的一些代码以使用此方法重构几个if语句时,我只是注意到了这一点。有一个if语句利用&&进行短路评估(如果第一部分为false,第二部分将抛出NPE),并且我在重构该代码时失败了(因为它总是对两者进行评估并将其存储在布尔值中)变量)。好点,谢谢!但是我在尝试时想知道-是否有任何方法可以将逻辑存储在变量中并使它延迟评估直到实际的if语句?
弗罗蒂2010年

2
我实际上怀疑编译器会解决该问题。如果第二个调用无效,通常是由于调用某些函数引起的,并且AFAIK没有编译器试图确定被调用函数是否没有副作用。
JS 2010年

您可以嵌套IF,除非第一次测试不足以决定是否继续进行,否则不要进行后续计算。
杰伊,2010年

@froadie有些语言,例如Kotlin(以及后来的Dart)允许“惰性”变量,这些变量仅在使用时才计算。另外,将逻辑放在函数中而不是变量中将具有相同的效果。只是,如果您十年后仍想知道,就知道了。
hacker1024 '20

2

我认为最好创建函数/方法而不是临时变量。这种方式还提高了可读性,因为方法变得更短。Martin Fowler的书《重构》为提高代码质量提供了很好的建议。与您的特定示例相关的重构称为“用查询替换温度”和“提取方法”。


2
您是在说通过将许多一次性功能弄乱类空间来提高可读性吗?请解释。
扎诺(Zano)

这始终是一个权衡。原始功能的可读性将提高。如果原始功能很短,则可能不值得。
mkj 2010年

我认为“混乱的类空间”也取决于所使用的语言以及如何对代码进行分区。
mkj

2

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


1

如果该方法要求成功通知:(C#中的示例)我喜欢使用

bool success = false;

开始。直到我将代码更改为:

success = true;

然后最后:

return success;

0

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

我应该不同意Kevin在他先前的文章中。我想他的例子可以用,以防万一,可以更改引入的变量,但是只为一个静态布尔值引入变量是没有用的,因为我们在方法声明中有参数名,为什么还要在代码中重复它呢?

例如:

void DoSomeMethod(boolean needDelete) { ... }

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

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

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

-1。老实说,这与原始问题有一点关系。问题是关于另一种非常具体的问题,而不是关于一般如何编写好的代码。
P Shved

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进来。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.