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

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个回答

14

我很高兴:

foreach (Foo f in foos)
  foreach (Bar b in bars)
    if (f.Equals(b))
      return true;

return false;

就我个人而言,我不明白为什么

foreach (Foo f in foos)
{
  foreach (Bar b in bars)
  {
    if (f.Equals(b))
    {
      return true;
    }
  }
}

return false;

读起来更加易懂。

是的,代码可以换行自由分段,但为什么我要滚动屏幕浏览几页代码,而它本可以只有一半大小呢?

如果加上花括号确实能提高可读性或可维护性,那当然要加... 但在这种情况下,我不认为有任何理由要加。

此外,对于嵌套的if语句和嵌套的else语句,我会始终加上花括号。

if (condition1)
  if (condition2)
    doSomething();
  else (condition2)
    doSomethingElse();

vs

的翻译是:vs。
if (condition1)
  if (condition2)
    doSomething();
else (condition2)
  doSomethingElse();

它非常令人困惑,所以我总是这样写:

if (condition1)
{
  if (condition2)
    doSomething();
  else (condition2)
    doSomethingElse();
}

尽可能使用三元运算符,但我从不嵌套使用它们


刚在我的代码中看到了这个问题:) 想研究一下,结果发现...已经有人解决了 :) - Noctis

14

我曾经也有过同样的想法。

直到有一天(为什么总有那个“一天”会永远改变你的生活?),我们连续24-36小时不间断地在调试生产代码,结果发现有人没有加括号并结合了搜索/替换操作。

就像这样:

 if( debugEnabled ) 
      println( "About to save 1 day of work to some very important place.");
 saveDayData();

之后出现的是什么

 if( debugEnabled ) 
 //     println( "About to save 1 day of work to some very important place.");
 saveDayData();

事实证明,系统每天会生成500MB的日志,我们被要求停止这种情况。使用调试标志是不够的,因此需要进行搜索和替换println操作。

尽管如此,当应用程序进入生产环境时,调试标志已关闭,并且重要的“saveDayData”从未被调用。

编辑

现在,我只有在if / try结构中不使用大括号。

if( object != null ) try { 
     object.close();
} catch( .....

在看了一位超级开发者这样做之后。


3
我不理解。你有一个控制调试的变量(debugEnabled),但仍然使用注释来禁用调试?为什么不直接将debugEnabled设置为false? - Igor Popov
这不完全是这种情况,但你说得很对。愚蠢的事情(比如没有将调试设置为false)确实会产生微妙且难以发现的愚蠢错误,这比“明显”的错误更难找到。在这种情况下,不使用大括号也没有太多帮助。 - OscarRyz
1
@igor 他们只是想删除一行日志而不是所有日志,这有什么问题吗? - gman

12

在计算机编程领域(就是你们这些人)中,当你跳过单行块的大括号时,我对我的同行们不被潜在错误吓倒感到钦佩和谦卑。

我想这意味着我不够聪明。我在这方面犯过多次错误。我调试过别人因为这个犯的错误。我看着软件因为这个而有bug(远程桌面连接到运行VS2002的机器上,你的监视窗口字体会变成奇怪的样子)。

如果我考虑一下所有我曾经犯过的可以通过改变编码风格避免的错误,那列表会非常长。如果我没有在每个案例中改变我的方法,我可能永远都不会成为程序员了。再次说明,我想我不够聪明。为了弥补这一点,我长期以来一直坚定地使用单行块的大括号。

话虽如此,世界上有些事情已经发生了变化,使得“单行块应该使用大括号”的规则比摩西把它带给我们时不那么相关了:

  • 一些流行的语言通过让计算机读取缩进,就像程序员一样(例如Python),解决了这个问题。

  • 我的编辑器自动格式化,这样我被缩进误导的机会就大大降低了。

  • TDD表示,如果我因为单行块而感到困惑而引入了bug,我很快就能发现它。

  • 重构和语言表达力意味着我的块更短,单行块比以前更常见。假设残酷地应用ExtractMethod,我的整个程序可能只有单行块。(我想知道那会是什么样子?)

事实上,在单行块中无情地进行重构和省略大括号可以带来明显的好处:当你看到大括号时,脑海中可以闪过一个小警报,“复杂性在这里!当心!”。想象一下,如果这是常规做法:

if (condition) Foo();   // normal, everyday code

if (condition) 
{
    // something non-trivial hapening; pay attention!
    Foo();
    Bar();
}

我正在考虑采用像“单行块可能永远不会有大括号”或“如果你可以将块放在条件的同一行,并且所有内容都适合80个字符,则省略大括号”的编码约定更改的想法。我们会看到。


作为一名曾经的Java程序员,我必须完全同意自动格式化是解决问题的真正答案。你甚至不需要让你的编辑器自动添加大括号——仅自动缩进就可以帮助避免很多歧义。当然,现在我已经转向Python,我已经习惯了首先正确地格式化我的代码。 - Alan Plum
我对大括号的看法也是如此。这完全取决于复杂性。理想情况下,我们只需要一行代码块。这才是可读性,而不是不必要的大括号。 - Igor Popov

11

我同意“如果你足够聪明让别人付钱让你写代码,那么你应该聪明到不仅仅依靠缩进来看代码的流程。”

然而...错误是会犯的,而且这个错误很难调试...尤其是当你要查看别人的代码时。


10

我的哲学是,如果它能让代码更易读,为什么不这么做呢?

显然,你必须在某个地方划定界限,比如在简洁和过度描述的变量名称之间寻找平衡点。但方括号确实可以避免错误并提高代码的可读性。

你可以争论说,聪明到可以成为程序员的人将足够聪明,以避免源自无方括号语句的错误。但你能真诚地说你从未因一个拼写错误而被绊倒吗?在查看大型项目时,这种细节可能会让人不知所措。


7
当然,这是非常主观的。有时候省略它们可以提高可读性。 - Brian Knoblauch
如果我忘记加括号,我会像其他人提到的那样保留一行。我被多行无括号语句咬了太多次。即使你知道要注意它,有时仍然会出现问题。 - James McMahon

10

总会有例外,但我认为在以下形式中不应省略大括号:

if(x == y)
   for(/* loop */)
   {
      //200 lines
   }

//rampion's example:
for(/* loop */)
{
   for(/* loop */)
      for(/* loop */)
      {
         //several lines
      }
}
否则,我对此没有任何问题。

4
不好的例子。在这两种情况下,我会将那个有200行的循环拆分成单独的方法,最好是几个方法。 - Adam Jaskiewicz
2
是真的,但它确实会发生。此外,每当代码块超过读者屏幕高度时,你都会遇到这个问题。而且你无法控制你的代码阅读器的屏幕高度。他们可能只能看到每一侧几行。 - rampion
2
确实会发生。但这并不意味着它应该发生。 - Adam Jaskiewicz
3
没错,这确实会发生。我们需要考虑发生了什么事情,而不是应该发生什么事情。如果我们能够限制自己只关注应该发生的事情,那么我们就不需要担心漏洞问题。 - Beska

10

我偶尔会使用最下面的代码(多个using语句),但除此之外,我总是放置大括号。我只是觉得这样可以使代码更清晰。很明显,不仅仅是缩进可以看出一个语句是否属于一个块(因此可能是if等的一部分)。

我见过...

if (...)
    foo();
    bar();

有一次我被bug咬了(或者说是“我和同事们” - 实际上我没有引入这个bug)。

尽管当时我们的编码标准建议在所有地方都使用括号,但我还是被咬了一次。令人惊讶的是,我花了很长时间才发现它 - 因为你看到的是你想看到的。(这是大约10年前的事情。也许现在我会更快地发现它。)

当然,如果你使用“在行末放置括号”的风格,可以减少额外的代码行数,但我个人不喜欢这种风格。(我在工作中使用它,并发现它比我预期的要好,但还是有点不舒服。)


我一直认为一致性比实际风格更重要,为什么我们只在使用情况下例外呢? - Bob
@Bob:说得好,我只是偶尔这样做。我不想假装我有实际的理由 :) 实际上,有一个合理的原因——像这样嵌套使用(仅限于使用)是相当明显的,而且你仍然会得到一个块。我目前看不出有什么危险。 - Jon Skeet
当然,并不是说没有任何危险。 - Jon Skeet
1
最佳解决方案:使用Python。(当然是开玩笑的。) - Joel Wietelmann

9

在这三个约定中:

if(addCurleyBraces()) bugFreeSofware.hooray();

并且:

if(addCurleyBraces())
    bugFreeSofware.hooray();

并且(代表使用开放和关闭大括号表示任何缩进样式):

if(addCurleyBraces()) {
    bugFreeSofware.hooray();
}

我更喜欢最后一种写法,原因如下:

  • 如果所有if语句都以统一的方式编写,我觉得阅读起来更容易。
  • 虽然这样可能使软件稍微更加健壮和减少bug,但是所有现代IDE和高级文本编辑器都具有良好的自动缩进功能,只要不破坏注释格式或违反团队规范(在许多情况下,可以创建自定义格式方案并与团队共享),我认为每个人都应该使用。我的观点是,如果正确缩进,可以稍微减少引入错误的风险。
  • 我更喜欢将布尔表达式和执行语句放在不同的行上。我喜欢能够标记一个用于调试的行。即使我使用的是可以标记语句并跳转到它的IDE,这是一个交互式操作,我可能会忘记从哪里开始调试,或者至少需要花费更多的时间多次步进代码(因为我必须在每次调试时手动标记位置)。

8
您反对使用大括号的主要理由是它们会增加额外的行数,并且需要额外的缩进。
行数(几乎)不会影响代码的性能,因此最小化代码行数不应该是一个目标。
另外,缩进与大括号的使用无关。在您的级联 'using' 示例中,即使省略大括号,我仍然认为您应该对它们进行缩进。

8
我坚信写整洁简明的代码,但我总是使用花括号。我发现它们是一种快速查看特定代码行所在作用域的便捷方式。没有歧义,它就在你面前明确地设置出来。
有人可能会说这是偏好的问题,但我发现如果程序内部一致性很强,那么逻辑流程会更容易跟踪,而且我不认为像这样写一个IF语句是一致的;
if(x < y)
    x = y;
else
    y = x;

还有一个类似这样的;

if(x < y)
{
    x = y;
    x++;
}
else
{
    y = x;
    y++;
}

我更喜欢选择一种通用的样式并坚持使用它 :)

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