为什么省略花括号被认为是一种不好的做法?

188

为什么每个人都告诉我像这样编写代码是一种不良实践呢?

if (foo)
    Bar();

//or

for(int i = 0 i < count; i++)
    Bar(i);

我认为省略花括号的最大优点是,有时候使用花括号会造成代码行数翻倍。例如,下面是用C#绘制标签发光效果的一些代码:

using (Brush br = new SolidBrush(Color.FromArgb(15, GlowColor)))
{
    for (int x = 0; x <= GlowAmount; x++)
    {
        for (int y = 0; y <= GlowAmount; y++)
        {
            g.DrawString(Text, this.Font, br, new Point(IconOffset + x, y));
        }
     }
 }
 //versus
using (Brush br = new SolidBrush(Color.FromArgb(15, GlowColor)))
    for (int x = 0; x <= GlowAmount; x++)
        for (int y = 0; y <= GlowAmount; y++)
            g.DrawString(Text, this.Font, br, new Point(IconOffset + x, y));

您还可以获得链接 usings 的附加好处,而无需缩进数百万次。

using (Graphics g = Graphics.FromImage(bmp))
{
    using (Brush brush = new SolidBrush(backgroundColor))
    {
        using (Pen pen = new Pen(Color.FromArgb(penColor)))
        {
            //do lots of work
        }
    }
 }
//versus
using (Graphics g = Graphics.FromImage(bmp))
using (Brush brush = new SolidBrush(backgroundColor))
using (Pen pen = new Pen(Color.FromArgb(penColor)))
{
    //do lots of work
}

花括号最常见的使用是为了维护编程,并避免在原始if语句和其预期结果之间插入代码带来的问题:

if (foo)
    Bar();
    Biz();

问题:

  1. 想使用语言提供的更紧凑的语法有错吗?设计这些语言的人很聪明,我无法想象他们会加入一种总是不好使用的特性。
  2. 我们应该还是不应该编写代码让最低公共分母能够理解并且没有问题地与之工作?
  3. 还有其他的论点我错过了吗?

8
我同意你的看法。省略它们。句号。 - Andrei Rînea
77
谁会在2010年关心某个东西有多少行啊?现在的显示器都很宽、很便宜,而且分辨率很高!我的显示器是2048 x 1152,而且我有两个!可读性比节省2个垂直线更重要,因为你很容易引入难以发现的细微错误。 - user177800
56
显示器宽而便宜,但不高也不便宜。垂直空间比水平空间更为紧缺。 - Adam Ruth
38
把它们横过来 :) - Zachary Yates
17
为了避免像苹果在2014年2月发现的SSL漏洞那样出糗,哈哈。 - learnvst
显示剩余15条评论
52个回答

7

好的,这是一个被问烂了的老问题。我有一些需要补充的内容。

首先,我必须说使用大括号。它们只能帮助可读性,而可读性(对于你和其他人!)应该非常高于你的优先事项列表,除非你正在编写汇编代码。不可读的代码总是会导致 bug。如果你发现大括号使你的代码占用太多空间,那么你的方法可能太长了。如果你做得正确,大部分或全部方法应该适合一个屏幕高度,并且查找(F3)是你的朋友。

现在是我的补充:

if (foo) bar();

尝试设置一个断点,只有当bar()将要运行时才会命中。在C#中,您可以将光标放在代码的后半部分以实现此操作,但这并不明显且有些麻烦。在C++中,您根本无法做到这一点。我们最资深的C++开发人员之一坚持出于这个原因将“if”语句拆分为两行。我也同意他的观点。

所以这样做:

if (foo)
{
    bar(); //It is easy to put a breakpoint here, and that is useful.
}

2
右键单击栏并选择插入断点...一点也不难。熟悉你的工具。 - Bob
单击指示器栏无效,您是要右键单击文本吗?无论如何,如果您只想在“if”语句为真且“bar()”位于其自己的行上时才触发断点,可以将光标放在该行的任何位置并按F9,或单击指示器边距。但是,如果所有代码都在一行上,则必须将光标定位在“bar()”上或者在那里精确地右键单击,然后再按F9,单击指示器边距不起作用(会在“if”上放置断点)。这并不“难”,但当代码分成两行时需要更少的注意力。 - stone
哦,哈哈,在“bar()”上右键点击。你是指这个文本。明白了。当然,或者只需按F9键... - stone
你必须将光标定位在“bar()”上或者准确地右键单击那里,然后再按F9键。 (我的意思是在按F9键或右键单击之前,必须将光标定位在那里) - stone

7

其中一个主要问题是当你有单行和非单行组成的区域,以及控制语句(forif等)与语句结尾之间的分离。

例如:

for (...)
{
  for (...)
    for (...) 
    {
      // a couple pages of code
    }
  // which for block is ending here?  A good text editor will tell you, 
  // but it's not obvious when you're reading the code
}

4
你的for循环中为什么会有几页的代码呢? - Adam Jaskiewicz
因为我是一个不敏感的笨蛋,而且我着迷于消除函数调用的“成本”。 :) - rampion
4
这几页的代码通常应该放在一个单独的函数中。 - Jonathan Leffler
1
对不起,我的意思是我扮演了这样一个笨蛋的角色。然而,我认为当通过小视口查看较短的区域时,同样的问题也可能会发生。这是程序员无法控制的。 - rampion

7

我曾坚定支持“花括号必须存在”的观点,但自从采用单元测试后,我发现我的单元测试可以保护无花括号语句免受以下情况的影响:

if (foo)
    snafu();
    bar();

通过良好的单元测试,我可以自信地省略简单语句中的大括号,以提高可读性(是的,这可能是主观的)。

或者,对于上面的示例,我可能会将其内联为:

if (foo) snafu();

这样,需要将bar()添加到条件中的开发人员更容易识别缺少花括号,并添加它们。


2
你说过“我不需要它”,但却给出了一个使用它的理由:可读性! - tvanfosson
4
我不会依靠单元测试来发现那个问题。LINT 可能可以,但单元测试不行! - Kristen
2
@Kristen - 单元测试的想法是断言方法的行为。如果行为发生变化(如上所述),单元测试将失败。我不认为这有什么问题。 - Liggy
但是为什么要依赖单元测试来发现那些在进行 UT 之前应该修复的明显问题呢? - Andrew

6

在适当的情况下,比如在你的第一个例子中,我总是省略它们。干净、简洁的代码可以通过一瞥就看得懂和理解,比我必须逐行阅读和滚动的代码更容易维护、调试和理解。我认为大多数程序员都会同意这一点。

如果你开始进行多重嵌套、if/else语句等等,很容易失控,但我认为大多数程序员应该能够知道在哪里划线。

我认为这有点像if ( foo == 0 )if ( 0 == foo )的争论。后者可能会为新手程序员(甚至经验丰富的程序员偶尔也会)防止出现错误,而前者在维护代码时更容易快速阅读和理解。


6

减少代码行并不是放弃使用大括号的好理由。如果你的代码过于庞大,应该重构成更小的模块或重新组织。这样做无疑会比简单去掉大括号更能提高可读性。


6
假设您有一些代码:

假设您有一些代码:

if (foo)
    bar();

然后另一个人加入并添加:

if (foo)
    snafu();
    bar();

根据代码编写的方式,bar();现在是无条件执行的。通过包含花括号,可以防止这种意外错误。代码应该以这样的方式编写,使得这种错误难以发生或不可能发生。如果我进行代码审查并看到缺少花括号,特别是在多行中分散出现的情况下,我会创建一个缺陷。在有正当理由的情况下,将其保留在一行上,以便再次将发生这种错误的机会降至最低。


那个观点已经在问题中提出了。 - Mike Scott
其实不完全是这样。是的,他提到了这种错误的潜在可能性,但我所说的是将使您的代码防错并消除潜在错误作为最佳实践的一部分。 - Elie
真的有所谓的无错代码吗? - Bob
不可以,但你可以让它变得更加困难。阅读Jason Cohen的《同行代码审查的最佳保密技巧》(请参见此处某些页面旁边的链接),以更好地了解为什么要这样做。 - Elie
这个Mike Scott是谁,为什么他是答案警察?总是让我笑翻了,人们总是要说出显而易见的事情。那么为什么Mike没有继续评论用户对这个问题添加的描述呢?难道所有的回答不都是在回应问题吗? - bruceatk

6
为了避免代码中的花括号占用大量空间,我使用了书籍《代码大全》中推荐的技巧:
if (...) {
    foo();
    bar();
}
else {
    ...
}

我不明白为什么它被降级了,它可以为每个括号对节省一行,我一直在使用它。 - Eric
2
这更普遍地被称为K&R风格。 基于Kernighan和Ritchie的书《C程序设计语言》:http://en.wikipedia.org/wiki/The_C_Programming_Language_(book)。 它不应该使您被评定降级。 - jmucchiello
谢谢!我觉得这只是个人口味问题。以前我自己也觉得这种语法很烦人,但是在读完《代码大全》之后,我采用了它,并且现在真的很喜欢它。 - Nathan Prather
我读了这个答案上面的所有答案,心想“为什么还没有人建议这个呢?”它使得闭合括号与其所关闭的块(如“if”或“while”等)对齐,而不是一个无意义的“{”。+1。 - nickf
如果大括号总是单独放在一行上,那么后面跟着一个缩进的单行的 if 语句可以被视为控制单个语句而无需任何大括号。这种单独放置大括号的风格因此可以节省空白,特别是当 if 语句控制的内容在语义上是单个操作(与仅包含单个步骤的序列不同)时。例如,if (someCondition) / throw new SomeException(...) - supercat
节省垂直空间并非一种美德。 - Almo

6

运用一些个人判断。

if (foo)
  bar();

这本身是没问题的。除非你真的担心有些蠢货会在后面加入像这样的内容:

if (foo)
  bar();
  baz();

如果你不担心白痴,那么没问题(我不担心——如果他们不能正确使用基本的代码语法,这只是他们问题中最小的一部分)。
作为交换,它更易读。
其余时间:
if (foo) {
  bar();
  baz();
}

这是我记忆中最喜欢的。此外:

if (foo) {
  bar();
  baz();
} else {
  qux();
}

对我来说可以。

垂直空间本身并不是非常重要,可读性才是关键。将左大括号单独放在一行上只会为语法元素停止对话,直到您的视线移动到下一行。这不是我喜欢的方式。


我只有在需要立即返回或在出现错误时抛出异常的情况下才会使用第一种风格,例如 if (parameter == null) return null; - nawfal

5

以更加安全为主 - 多考虑一点,就有可能不需要修复一个bug。

我个人认为,如果所有的代码块都用大括号括起来,我会感到更加安全。即使是一行代码,这些简单的注释也可以防止错误。从可读性的角度来看,它使得代码中的块内容清晰明确,不会将块的内容与块外的后续语句混淆。

如果我有一个一行代码的块,通常会按照以下格式进行排版:

if( some_condition ) { do_some_operation; }

如果这一行文字太长了,可以使用以下方式:
if( some_condition )
{
    do_some_operation;
}

4
大多数情况下,无论是为公司还是自由开源项目,都会将其作为编码规范深入人心。毕竟,其他人需要理解您的代码,并且每个开发人员都要花费大量时间来弄清楚他们正在处理的代码部分的特定样式。此外,想象一下有人一天内在Python和Cish语言之间多次切换... 在Python中,缩进是语言块语义的一部分,容易犯像您引用的那种错误。

+1 Python的评论。当我不断切换语言时,我总是惊讶于自己有多“愚蠢”。 - Kena

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接