关于if语句:“else”在Python中被认为有害吗?

“else” considered harmful in Python?

在回答(S.lott)关于python的try...else声明的问题时:

Actually, even on an if-statement, the
else: can be abused in truly terrible
ways creating bugs that are very hard
to find. [...]

Think twice about else:. It is
generally a problem. Avoid it except
in an if-statement and even then
consider documenting the else-
condition to make it explicit.

这是一个广泛持有的观点吗?else被认为有害吗?

当然,您可以用它编写令人困惑的代码,但对于任何其他语言结构来说,这是正确的。在我看来,即使是python的for...else也是非常方便的(对于try...else就不那么方便了)。


洛特显然看到了一些坏代码。我们不是吗?我不认为其他有害的东西,尽管我见过它曾经写坏代码。在这些情况下,所有周围的代码都是坏的,那么为什么要怪穷人呢?


不,这不是有害的,是必要的。

应该总是有一个catch all语句。所有开关都应该有一个默认值。ML语言中的所有模式匹配都应该有一个默认值。

如果一系列的if语句是生活中的一个事实,那么就不可能推断出什么是真的了。计算机是世界上最大的有限状态机,在任何情况下列举每一种可能性都是愚蠢的。

如果你真的担心在else语句中未知的错误会被忽略,那么在那里引发异常真的那么困难吗?


我不会说这是有害的,但有时候,else语句会给你带来麻烦。例如,如果您需要基于输入值进行一些处理,并且只有两个有效的输入值。只有检查一个才能引入错误。如:

1
2
3
4
5
6
7
8
9
10
11
12
The only valid inputs are 1 and 2:

if(input == 1)
{
   //do processing
   ...
}
else
{
   //do processing
   ...
}

在这种情况下,使用else将允许处理除1以外的所有值,而只应处理值1和2。


说其他的被认为是有害的,有点像说变量或类是有害的。见鬼,这甚至像是说goto是有害的。当然,事情可能会被误用。但在某些时候,你只需要相信程序员是成年人,并且足够聪明就可以了。

归根结底是这样的:如果你不愿意使用某个东西,因为某物上的答案或博客文章,甚至是Dijkstra的一篇著名论文告诉你不要这样做,你需要考虑编程是否是适合你的职业。


对我来说,某些流行语言结构本身就不好的整个概念是完全错误的。即使是goto也有它的位置。我已经看到了非常可读、可维护的代码,比如WalterBright和使用它的LinusTorvalds。仅仅教程序员可读性很重要,并且使用常识,比任意地声明某些结构"有害"要好得多。


如果你写:

1
2
3
4
5
if foo:
    # ...
elif bar:
    # ...
# ...

那么读者可能会想:如果foobar都不是真的呢?也许你知道,从你对代码的理解来看,要么是foo要么是bar。我想看看:

1
2
3
4
5
6
if foo:
    # ...
else:
    # at this point, we know that bar is true.
    # ...
# ...

或:

1
2
3
4
5
6
if foo:
    # ...
else:
    assert bar
    # ...
# ...

这就向读者清楚地表明了您希望控制如何流动,而不需要读者对foobar的来源有深入的了解。

(在最初的情况下,你仍然可以写一篇评论来解释正在发生的事情,但我想我会想:"为什么不直接使用else:条款?"

我认为重点不是你不应该使用else:;相反,一个else:子句可以让你写不清楚的代码,你应该试着认识到什么时候会发生这种情况,并添加一些注释来帮助任何读者。

在编程语言中,大多数事情都是这样的,实际上是:—)


相反…在我看来,每一个国际单项体育联合会都必须有另外一个。当然,你可以做愚蠢的事情,但是如果你足够努力,你可以滥用任何构造。你知道"一个真正的程序员可以用每种语言编写Fortran"这句话。

我做的很多时间是写另一部分作为评论,描述为什么没有什么可以做。


在记录关于代码的假设时,else最有用。它确保您已经考虑了if语句的两边。

在"代码完成"中,总是在每个if语句中使用else子句甚至是推荐的做法。


首先,在python中包含else语句(try...else语句)的基本原理是只捕获您真正想要的异常。通常,当您有一个try...except块时,会有一些代码可能引发异常,然后会有更多的代码只有在前一个代码成功时才能运行。如果没有else块,则必须将所有代码放入try块:

1
2
3
4
5
try:
    something_that_might_raise_error()
    do_this_only_if_that_was_ok()
except ValueError:
    # whatever

问题是,如果do_this_only_if_that_was_ok()提出ValueError会怎么样?它可能会被except声明捕获,而你可能不想这样做。这就是else区块的目的:

1
2
3
4
5
6
try:
    something_that_might_raise_error()
except ValueError:
    # whatever
else:
    do_this_only_if_that_was_ok()

我想在某种程度上这是一个意见问题,但我个人认为这是一个好主意,尽管我很少使用它。当我使用它时,感觉非常合适(而且,我认为它有助于澄清代码流)


在C族语言中存在一个所谓的"悬空-其他"问题,如下所示:

1
2
3
4
5
if (a==4)
if (b==2)
printf("here!");
else
printf("which one");

这种无辜的代码可以通过两种方式理解:

1
2
3
4
5
if (a==4)
    if (b==2)
        printf("here!");
    else
        printf("which one");

1
2
3
4
5
if (a==4)
    if (b==2)
        printf("here!");
else
    printf("which one");

问题是"另一个"是"悬空的",人们可能会混淆另一个的所有者。当然,编纂者不会造成这种混乱,但对凡人是有效的。

多亏了python,我们在python中不能有其他悬而未决的问题,因为我们也必须编写

1
2
3
4
5
if a==4:
    if b==2:
        print"here!"
else:
    print"which one"

1
2
3
4
5
if a==4:
    if b==2:
        print"here!"
    else:
        print"which one"

这样人类的眼睛就能捕捉到它。而且,不,我不认为"其他"是有害的,它和"如果"一样有害。


在我看来,对于任何语言和任何有默认方案或副作用的流控制语句,该方案都需要具有相同的考虑级别。if或switch或while中的逻辑仅与if(x)while(x)或for(…)中的条件相同。因此,该语句并不有害,但其条件中的逻辑是。

因此,作为开发人员,我们有责任在编写代码时考虑到其他的广泛范围。太多的开发人员将其视为"如果不是上面的话",而实际上它可以忽略所有常识,因为其中唯一的逻辑是对前面的逻辑的否定,这通常是不完整的。(算法设计错误本身)

然后,我不会认为"else"比for()循环或糟糕的内存管理中的"off"更有害。这都是关于算法的。如果您的自动机在其作用域和可能的分支中是完整的,并且所有这些都是具体的和理解的,那么就没有危险。危险在于人们没有意识到宽范围逻辑的影响而滥用表达式背后的逻辑。计算机是愚蠢的,他们做操作员告诉他们的事情(理论上)

我确实认为尝试和捕获是危险的,因为它会否定对未知数量的代码的处理。提升上方的分支可能包含一个bug,由提升本身突出显示。这是不明显的。这就像将一组连续的指令转换成一个错误处理树或图,其中每个组件都依赖于父级中的分支。奇怪的。注意,我爱C。


我认为关于try...except...else的要点是,使用它来创建不一致的状态而不是修复它是一个容易犯的错误。这并不是说应该不惜一切代价避免它,而是它可能适得其反。

考虑:

1
2
3
4
5
6
7
8
try:
    file = open('somefile','r')
except IOError:
    logger.error("File not found!")
else:
    # Some file operations
    file.close()
# Some code that no longer explicitly references 'file'

如果说上面的块阻止了代码试图访问不存在的文件或用户没有权限的目录,并且说所有内容都是封装的,因为它在一个try...except...else块中,那就太好了。但实际上,上面形式的很多代码实际上应该是这样的:

1
2
3
4
5
6
7
8
try:
    file = open('somefile','r')
except IOError:
    logger.error("File not found!")
    return False
# Some file operations
file.close()
# Some code that no longer explicitly references 'file'

你经常自欺欺人地说,因为file不再在作用域中引用,在块后继续编码是可以的,但在许多情况下,会出现一些不好的情况。或者可能稍后在else块中创建一个变量,而该块不是在except块中创建的。

这就是我如何区分if...elsetry...except...else。在这两种情况下,在大多数情况下,必须使块并行(一个中的变量和状态集应该在另一个中设置),但在后者中,编码人员通常不这样做,可能是因为这是不可能的或不相关的。在这种情况下,返回给呼叫者通常比继续在最佳情况下处理您认为的问题更有意义。


在这个被假定为难以推理的例子中,它可以被明确地写出来,但是其他的仍然是必要的。例如。

1
2
3
4
5
6
7
if a < 10:      
    # condition stated explicitly  
elif a > 10 and b < 10:      
    # condition confusing but at least explicit  
else:      
    # Exactly what is true here?      
    # Can be hard to reason out what condition is true

可以写

1
2
3
4
5
6
7
8
if a < 10:      
    # condition stated explicitly  
elif a > 10 and b < 10:      
    # condition confusing but at least explicit  
elif a > 10 and b >=10:
    # else condition
else:  
    # Handle edge case with error?