对于单行if或循环使用大括号(即{})的目的是什么?

JAN*_*JAN 313 c c++ defensive-programming coding-style curly-braces

我正在阅读我的C++讲师的一些讲义,他写了以下内容:

  1. 使用缩进//确定
  2. 永远不要依赖运算符优先级 - 始终使用括号//确定
  3. 总是使用{}块 - 即使是单行// 不行,为什么???
  4. 比较左侧的Const对象// OK
  5. 对于> = 0 //好玩法的变量使用无符号
  6. 删除后将指针设置为NULL - 双删除保护//不错

第三种技术对我来说并不清楚:通过在一条线中放置一条线可以获得{ ... }什么?

例如,拿这个奇怪的代码:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}
Run Code Online (Sandbox Code Playgroud)

并替换为:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
Run Code Online (Sandbox Code Playgroud)

使用第一个版本有什么好处?

Luc*_*ore 505

i我们在增加时尝试修改j:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
        i++;
Run Code Online (Sandbox Code Playgroud)

不好了!来自Python,这看起来不错,但实际上并非如此,因为它相当于:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
    if (i % 2 == 0)
        j++;
i++;
Run Code Online (Sandbox Code Playgroud)

当然,这是一个愚蠢的错误,但即使是有经验的程序员也可以做出错误.

ta.speot.is的回答中指出了另一个很好的理由.

我能想到的第三个是嵌套if的:

if (cond1)
   if (cond2) 
      doSomething();
Run Code Online (Sandbox Code Playgroud)

现在,假设你现在想要doSomethingElse()什么时候cond1不满足(新功能).所以:

if (cond1)
   if (cond2) 
      doSomething();
else
   doSomethingElse();
Run Code Online (Sandbox Code Playgroud)

这显然是错误的,因为else与内在联系在一起if.


编辑:由于这引起了一些关注,我将澄清我的观点.我回答的问题是:

使用第一个版本有什么好处?

我已经描述过了.有一些好处.但是,IMO,"永远"规则并不总是适用.所以我不完全支持

总是使用{}块 - 即使是单行//不行,为什么???

我不是说总是使用一个{}块.如果这是一个简单的条件和行为,不要.如果您怀疑有人可能会稍后进入并更改您的代码以添加功能,请执行此操作.

  • @James:这不是一个"事实",但这是你的工作流程.很多人的工作流程,但不是每个人的工作流程.我认为将C++源代码视为纯文本文件而不是*必然*错误,而不是强制执行格式规则的WYSIWYG编辑器(vi/emacs/Visual Studio)的输出.因此,这条规则与编辑无关,超出了您的需要,但不超出人们实际用于编辑C++的范围.因此"防守". (43认同)
  • @JamesKanze你是否真的依赖于每个人都在强大的IDE中工作的假设?最后一篇CI写的是Nano.即使考虑到这一点,我倾向于在IDE中关闭的第一件事就是自动缩进 - 因为IDE往往会妨碍我的_non-linear_工作流程,试图根据不完整的信息纠正我的'错误' .IDE不是很擅长自动缩进每个程序员的自然流程.那些使用这些功能的程序员倾向于将他们的风格合并到他们的IDE中,如果你只使用一个IDE,这很好,但如果你使用多个IDE则不是这样. (31认同)
  • 这听起来很合理,但忽略了编辑器执行缩进的事实,而不是你,并且它将以一种立即显示它不是循环的一部分的方式缩进`i ++;`.(在过去,这可能是一个合理的论点,我已经看到了这样的问题.大约20年前.从那以后.) (26认同)
  • "这是一个愚蠢的错误,但即便是经验丰富的程序员也可以做到." - 就像我在回答中说的那样,我不相信.我认为这是一个完全做作的案例,在现实中并没有造成问题. (10认同)
  • @Science_Fiction:是的,但是如果你在*`j ++`之前添加`i ++`*,那么当它们被使用时,两个变量仍将在范围内. (5认同)
  • @KonradRudolph你很幸运,因此无需修复bug.我有.在某些情况下,它并不明显. (4认同)
  • 这两个顶级答案如何通过他们的大多数荒谬的产品获得如此赞扬(以及问题如何变得如此受欢迎)完全超出我的范围; 我只需要假设C#和Java民众只是忽略了这个`c ++`问题. (3认同)
  • @JamesKanze:我敢打赌,很多程序员会把这两条指令写成一行,因为它们太短了.然后是kaboom. (2认同)
  • @SteveJessop C++ 源代码是“纯文本”,我经常这样对待它,例如在其上使用 `grep` 或 `diff`。但开发高质量的软件很困难,如果不使用我可用的所有工具,那就太愚蠢了。源代码编辑器比比皆是;当合适的工具唾手可得时,为什么要使用不合适的工具呢?(我见过的编辑器——你提到的三个——不强制执行格式规则。你总是可以覆盖它们。但是当隐式格式不符合你的期望时,这通常表明你犯了一个错误。) (2认同)

ta.*_*.is 320

如果你不使用{和,很容易用注释意外改变控制流}.例如:

if (condition)
  do_something();
else
  do_something_else();

must_always_do_this();
Run Code Online (Sandbox Code Playgroud)

如果您do_something_else()使用单行评论进行评论,则最终会得到以下结果:

if (condition)
  do_something();
else
  //do_something_else();

must_always_do_this();
Run Code Online (Sandbox Code Playgroud)

它编译,但must_always_do_this()并不总是被调用.

我们在代码库中遇到了这个问题,有人在发布之前很快就禁用了某些功能.幸运的是,我们在代码审查中发现了它.

  • 我前几天碰到了这个.`if(debug)\n // print(info);`.基本上拿出了整个图书馆. (20认同)
  • "幸运的是,我们在代码审查中发现了它.哎哟!这听起来很错误."幸运的是,我们在单元测试中发现了它.会好得多! (4认同)
  • @BЈовић但是,如果代码是在单元测试中怎么办?心灵难以置信.(开玩笑,这是一个遗留应用程序.没有单元测试.) (4认同)
  • 哦,小伙子!如果你注释// do_something_else(),它的定义行为是`must_always_do_this();` (3认同)

Jam*_*nze 59

我对讲师的能力表示怀疑.考虑他的观点:

  1. 有人真的会写(或想读)(b*b) - ((4*a)*c)?一些优先级是显而易见的(或应该是),而额外的括号只会增加混乱.(另一方面,即使您知道不需要它们,您也应该在不太明显的情况下使用括号.)
  2. 有点.格式化条件和循环有两种广泛的传播约定:
    if ( cond ) {
        code;
    }
    
    和:
    if ( cond )
    {
        code;
    }
    
    首先,我同意他的看法.开口{不是那么明显,所以最好假设它始终在那里.然而,在第二个中,我(和我一起工作的大多数人)在单个语句中省略大括号没有问题.(当然,前提是缩进是系统化的,并且你一直使用这种风格.(和许多非常优秀的程序员一样,编写非常易读的代码,即使在格式化第一种方式时也省略了大括号.)
  3. .像if ( NULL == ptr )丑陋的东西足以阻碍可读性.直观地写下比较.(在许多情况下导致右边的常数.)他的4是不好的建议; 任何使代码不自然的东西都会降低其可读性.
  4. .任何东西,但int保留特殊情况.对于有经验的C和C++程序员,使用unsigned信号位运算符.C++没有真正的基数类型(或任何其他有效的子类型); unsigned由于促销规则,对数值不起作用.没有算术运算有意义的数值,如序列号,可能是unsigned.但是,我反对它,因为它发出了错误的信息:按位操作也没有意义.基本规则是整数类型int,_unless_使用其他类型的重要原因.
  5. .系统地这样做是误导,实际上并没有防范任何事情.在严格的面向对象的代码,delete this;往往是最常见的情况(而不能设置thisNULL),否则,大多数delete都在析构函数,所以你不能访问指针后反正.并将其设置为对NULL浮动的任何其他指针没有任何作用.系统地设置指针NULL给出了一种虚假的安全感,并没有真正为你买任何东西.

查看任何典型参考文献中的代码.例如,Stroustrup违反了你给出的所有规则,除了第一个规则.

我建议你找另一位讲师.一个谁知道他在说什么.

  • @JamesKanze:我不得不说,我不同意你在这里所说的大部分内容 - 尽管我很欣赏并尊重你提出的要求.*对于有经验的C和C++程序员,使用无符号信号位运算符.* - 我完全不同意:使用*位运算符*表示使用位运算符.对我来说,使用`unsigned`表示程序员的*愿望*变量应该只表示正数.与带符号的数字混合通常会导致编译器警告,这可能是讲师的意图. (34认同)
  • *对于有经验的C和C++程序员,使用无符号信号位运算符*或不.`size_t`,有人吗? (18认同)
  • @Firedragon:大多数编译器会警告`if(ptr = NULL)`除非你把它写成`if((ptr = NULL))`.不得不同意James Kanze的说法,"NULL"的丑陋首先使它对我来说是一个明确的NO. (16认同)
  • @James Kanze,考虑目的.您正在将经验丰富的程序员生成的代码与教学示例进行比较.这些规则由讲师提供,因为他们看到了他的学生所犯的错误.凭借经验,学生可以放松或忽视这些绝对. (16认同)
  • 数字4可能很丑,但有目的.它试图阻止if(ptr = NULL).我不认为我曾经使用过`删除这个',它比我见过的更常见吗?我不倾向于认为在使用之后设置一个指向NULL的指针是除了YMMV之外的事情.也许这只是我,但他的大多数指导方针似乎并不那么糟糕. (13认同)
  • 我不同意你的许多观点(特别是3和5),但不管怎样,因为你表明讲师的教条列表无论如何都是错误的.这些规则,特别是在没有正当理由的情况下,只是坏事. (7认同)
  • 我希望删除这个答案,因为大多数答案和随后的讨论都与原始问题无关.这是噪音. (7认同)
  • @Kristopher不,它不是噪音,它显示了一个非常重要的方面:即,无论我们如何看待列表中的各个点,编译这样的列表作为教条一致被视为坏.可以说,这就是这个问题的答案. (7认同)
  • @JoshuaShaneLiberman其中一些(比如使用`unsigned`的建议)是不好的规则,特别是对于初学者.我非常了解积分推广的规则,我通常可以避免使用`unsigned`s陷阱,但初学者应该_only_看到两种数字类型:`double`和`int`.(形式上,`bool`和`char`也是数字类型,但是初学者不应该这样看:`bool`应该只取值`true`和`false`,`char`应该总是包含字符. ) (3认同)
  • @ Component10最后,你必须找到共识.问题是C++中`unsigned`的语义对数值不起作用,即使实际值不能为负数.例如,`abs(a - b)`这样的表达式给出了错误的结果. (2认同)
  • 我同意除了“对于有经验的 C 和 C++ 程序员,使用无符号信号位运算符”之外的所有内容 (2认同)
  • @Arne 不清楚你的观点是什么。编写良好的应用程序中的大多数指针将是原始指针,因为大多数指针将用于导航(并且 `std::unique_ptr` 不能用于导航)。将 `nullptr` 分配给刚刚删除的内容通常是在浪费时间;通常,`delete` 将在一个上下文(例如一个析构函数)中,在该上下文中指针无论如何都将不复存在,并且将一个指针设置为 `nullptr` 不会对指向该对象的其他指针执行任何操作。 (2认同)

Kon*_*lph 46

所有其他答案都捍卫了你的讲师规则3.

让我说我同意你的观点:这条规则是多余的,我不会建议.确实,如果你总是添加花括号,它理论上可以防止错误.另一方面,我从未在现实生活中遇到过这个问题:与其他答案所暗示的相反,我不会忘记在必要时添加大括号.如果你使用适当的缩进,很明显你需要在多个语句缩进时添加大括号.

"组件10"的答案实际上突出了唯一可以想象的情况,这可能真的导致错误.但另一方面,通过正则表达式替换代码总是需要非常小心.

现在让我们来看看奖牌的另一面:总是使用花括号是否有缺点?其他答案完全忽略了这一点.但是,一个缺点:它占用了大量的垂直屏幕空间,而这反过来又可以让你的代码不可读,因为这意味着你必须滚动超过必要的.

考虑一个开头有很多保护条款的函数(是的,以下是糟糕的C++代码,但在其他语言中,这将是一种非常常见的情况):

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
    {
        throw null_ptr_error("a");
    }
    if (b == nullptr)
    {
        throw null_ptr_error("b");
    }
    if (a == b)
    {
        throw logic_error("Cannot do method on identical objects");
    }
    if (not a->precondition_met())
    {
        throw logic_error("Precondition for a not met");
    }

    a->do_something_with(b);
}
Run Code Online (Sandbox Code Playgroud)

这是一个可怕的代码,我坚决认为以下内容更具可读性:

void some_method(obj* a, obj* b)
{
    if (a == nullptr)
        throw null_ptr_error("a");
    if (b == nullptr)
        throw null_ptr_error("b");
    if (a == b)
        throw logic_error("Cannot do method on identical objects");
    if (not a->precondition_met())
        throw logic_error("Precondition for a not met");

    a->do_something_with(b);
}
Run Code Online (Sandbox Code Playgroud)

类似地,短嵌套循环可以从省略大括号中获益:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
        for (auto j = 0; j < a.h(); ++j)
            c(i, j) = a(i, j) + b(i, j);

    return c;
}
Run Code Online (Sandbox Code Playgroud)

与之比较:

matrix operator +(matrix const& a, matrix const& b) {
    matrix c(a.w(), a.h());

    for (auto i = 0; i < a.w(); ++i)
    {
        for (auto j = 0; j < a.h(); ++j)
        {
            c(i, j) = a(i, j) + b(i, j);
        }
    }

    return c;
}
Run Code Online (Sandbox Code Playgroud)

第一个代码简洁; 第二个代码是膨胀的.

是的,通过将开口支撑放在前一行上可以在一定程度上减轻这种情况.但是,与没有任何大括号的代码相比,这仍然不太可读.

简而言之:不要编写占用屏幕空间的不必要代码.

  • 如果你不相信编写占用屏幕空间不必要的代码,那么你就没有业务*将开头括号放在自己的行上.我现在可能不得不躲避GNU的神圣复仇,但是说真的 - 要么你想让你的代码垂直紧凑,要么你不需要.如果你这样做,不要做专门为使你的代码不那么垂直紧凑而设计的东西.但正如你所说,修复了这个问题,你仍然*想要删除多余的括号.或者也许只写`if(a == nullptr){throw null_ptr_error("a"); }`作为一行. (26认同)
  • "我从未在现实生活中遇到过这个问题":幸运的是你.像这样的事情不只是烧你,他们给你90%的三度烧伤(这只是几层管理要求在晚上修复). (22认同)
  • +1我完全同意你的第一个例子更容易阅读没有大括号.在第二个例子中,我的个人编码风格是在外部for循环而不是内部使用大括号.我不同意@SteveJessop关于垂直紧凑代码必须是一个极端或另一个极端.我省略了使用单行的额外括号以减少垂直空间,但我确实将我的开口括号放在一个新行上,因为我发现当括号排成一行时更容易看到范围.目标是可读性,有时这意味着使用更多的垂直空间,有时则意味着使用更少的空间. (9认同)
  • @Richard我根本就不买.正如我在聊天中所解释的那样,即使发生了这个错误(我觉得不太可能发生),一旦你看到堆栈跟踪就很难解决,因为通过查看代码就可以明显看出错误.你夸大的说法完全是毫无根据的. (9认同)
  • @Steve事实上,由于你说的原因,我*确实*把前括号放在前一行.我在这里使用了另一种风格,使其更加明显的差异是多么极端. (6认同)
  • @Brian重点不在于*我*不会犯这个错误.关键在于我根本不相信*任何有经验的程序员会犯这个错误,或者*如果*他们成功的话,他们会立即抓住它并且没有任何伤害.理查德甚至声称这会带来很多艰苦的工作.我觉得这句话完全令人难以置信. (3认同)
  • [`goto fail`](http://www.zdnet.com/apples-goto-fail-tells-us-nothing-good-about-cupertinos-software-delivery-process-7000027449/) (3认同)
  • “我在现实生活中从未遇到过这个问题”:@Richard 是对的 - 这种事情往往会在最乏味的时刻表现出来。 (2认同)
  • 如果您编写的代码省略了大括号,并且代码被其他人修改了,而其中一些程序员不像您那么细心,我保证在某些时候会犯错误。也许您不必处理它,因为您永远不会犯错误,但如果您可以通过输入两个额外的字符来帮助防止将来出现错误,为什么不呢? (2认同)
  • @xfix 是的,“goto failed”是这个答案的完美反例。然而,我必须同意 Konrad Rudolph 的观点,即完全支撑的代码非常丑陋。就我个人而言,我不选择任何一种风格,而是将一个语句体直接放在“if()”或“for()”之后*同一行*。这可以防止像“goto failed”这样的错误,并且是编写此类代码的最简洁的方法。 (2认同)
  • @xfix 实际上,使用大括号根本无法阻止“goto 失败”。诚然,连*我*一开始也这么认为,但如果你仔细想一想——不。`goto failed;` 行可能是通过版本控制合并或某些类似过程添加的 - 即使在花括号代码中,它也可能在花括号“之后”,导致完全相同的问题。这里真正的教训是:使用适当的警告级别;*所有*现代编译器都会对这段代码发出警告。这本来就不应该通过审查。 (2认同)

jam*_*jam 39

我正在研究的代码库是由对病毒有病态厌恶的人分散的代码,而对于后来出现的人来说,它实际上可以对可维护性产生影响.

我遇到的最常见的问题是:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
    this_looks_like_a_then-statement_but_isn't;
Run Code Online (Sandbox Code Playgroud)

因此,当我出现并希望添加一个声明时,如果我不小心,我可以很容易地结束这个:

if ( really incredibly stupidly massively long statement that exceeds the width of the editor) do_foo;
{
    this_looks_like_a_then-statement_but_isn't;
    i_want_this_to_be_a_then-statement_but_it's_not;
}
Run Code Online (Sandbox Code Playgroud)

鉴于添加大括号需要大约1秒,并且可以在最少的几分钟调试时节省您的时间,为什么你不会使用简化模糊选项?对我来说似乎是虚假的经济.

  • 这个例子中的问题不在于不正确的缩进和太长的行而不是括号吗? (16认同)
  • 如何添加大括号(如果(真的很长...编辑){do_foo;}`帮助你避免这种情况?似乎问题仍然是相同的.我个人更喜欢在没有必要时避免使用括号,但是与写入它们所需的时间无关,但由于代码中有两个额外的行而导致可读性降低. (15认同)
  • 是的,但遵循设计/编码指南只是"安全",假设人们也遵循其他指导方针(例如没有太长的线路)似乎要求麻烦.如果支架从一开始就进入,那么在这种情况下就不可能得到一个不正确的if-block. (7认同)
  • 触摸文件时我做的第一件也是最后一件事就是按下自动格式按钮.它消除了大部分这些问题. (2认同)

Nem*_*vic 19

我的2c:

使用缩进

明显

永远不要依赖运算符优先级 - 始终使用括号

我不会使用"never and"always"这个词,但总的来说,我认为这个规则很有用.在某些语言中(Lisp,Smalltalk),这不是问题.

始终使用{}块 - 即使是一行

我从来没有这样做,从来没有遇到任何问题,但我可以看到它对学生有什么好处,尤其是.如果他们之前研究过Python.

比较左侧的Const对象

尤达的条件?不谢谢.它伤害了可读性.只需在编译代码时使用最大警告级别.

对于> = 0的变量使用无符号

好.有趣的是,我听说Stroustrup不同意.

删除后将指针设置为NULL - 双删除保护

不好的建议!永远不会有指向已删除或不存在的对象的指针.

  • 仅仅为最后一点+1.无论如何,原始指针没有业务拥有内存. (5认同)
  • 只是为了完成"`unsigned`被打破",其中一个问题是,当C++比较类似大小的有符号和无符号类型时,它会在进行比较之前转换为无符号的**.这会导致价值发生变化.转换为签名不一定会好得多; 比较应该真正发生"似乎"两个值都转换为更大的类型,可以代表任何一种类型的所有值. (3认同)
  • 关于使用无符号:不仅是Stroustrup,还有K&R(在C中),Herb Sutter和(我认为)Scott Meyers.事实上,我从未听过任何真正理解C++规则的人争论使用unsigned. (2认同)
  • @JamesKanze事实上,在同一场合,我听到了Stroustrup的意见(2008年的波士顿会议),Herb Sutter就在那里,当场不同意Bjarne. (2认同)
  • @SteveJessop我认为你必须将它放在返回“unsigned”的函数的上下文中。我确信他对“exp(double)”返回大于“MAX_INT”的值没有问题:-)。但再一次,真正的问题是隐式转换。`int i = exp( 1e6 );` 是完全有效的 C++。Stroustrup 实际上曾一度提议反对有损隐式转换,但委员会对此不感兴趣。(一个有趣的问题:`unsigned` -&gt; `int` 是否会被认为是有损的。我认为 `unsigned` -&gt; `int` 和 `int` -&gt; `unsigned` 都是有损的。这对于使`未签名` 好的 (2认同)

Alo*_*ave 17

它更直观,更容易理解.它使意图清晰.

并且它确保在新用户可能在不知不觉中错过时,代码不会中断{,}同时添加新的代码语句.


Com*_* 10 13

为了增加上面非常合理的建议,我在重构一些代码时遇到的一个例子如下:我正在改变一个非常大的代码库来从一个API转换到另一个API.第一个API调用了如下设置公司ID:

setCompIds( const std::string& compId, const std::string& compSubId );
Run Code Online (Sandbox Code Playgroud)

而替换需要两个电话:

setCompId( const std::string& compId );
setCompSubId( const std::string& compSubId );
Run Code Online (Sandbox Code Playgroud)

我开始使用非常成功的正则表达式来改变它.我们还通过astyle传递了代码,这使得它更具可读性.然后,在审查过程的一部分,我发现在某些条件情况下它改变了这个:

if ( condition )
   setCompIds( compId, compSubId );
Run Code Online (Sandbox Code Playgroud)

对此:

if ( condition )
   setCompId( compId );
setCompSubId( compSubId );
Run Code Online (Sandbox Code Playgroud)

这显然不是什么要求.我不得不回到开头再做一次,将替换完全视为一个块,然后手动修改最终看起来很傻的东西(至少它不会是错误的.)

我注意到astyle现在有一个选项--add-brackets,允许你在没有括号的地方添加括号,如果你发现自己处于和我相同的位置,我强烈推荐这个.

  • 我曾经看过一些文件,里面有精彩的硬币"Microsoftligent".是的,通过全局搜索和替换可能会出现重大错误.这只意味着必须智能地使用全局搜索和替换,而不是微软地使用. (5认同)
  • 我知道这不是我的验尸,但是如果你要在源代码上进行文本替换,那么你应该根据你用于那种已经建立良好的文本替换的相同规则来执行它.在语言中:宏.你不应该写一个宏`#define FOO()func1();\func2(); `(反斜杠后面有一个换行符),搜索和替换也一样.也就是说,我已经看到"总是使用大括号"高级作为样式规则,因为它可以避免在`do .. while(0)`中包装所有多语句宏.但我不同意. (4认同)
  • 顺便说一句,在日本虎杖很成熟的意义上说这是"完善的":我并不是说我们应该尽力使用宏和文本替换,但是我说当我们做这样的时候事情,我们应该以一种有效的方式来做,而不是做一些只有在特定的样式规则成功强加于整个代码库时才有效的东西:-) (2认同)
  • 而且我认为,您使用的样式越多,您阅读和编辑代码就越仔细。因此,即使您偏爱最容易阅读的内容,您仍然可以成功阅读其他内容。我在一家公司工作,不同的团队以不同的“房子风格”编写不同的组件,正确的解决方案是在午餐室抱怨它没有太大效果,而不是试图创建全局风格:-) (2认同)

Luk*_*don 8

{}除了少数显而易见的情况外,我到处都在使用.单行是其中一种情况:

if(condition) return; // OK

if(condition) // 
   return;    // and this is not a one-liner 
Run Code Online (Sandbox Code Playgroud)

在返回之前添加一些方法可能会对您造成伤害.缩进表示当满足条件时返回正在执行,但它将始终返回.

C#中使用statment的其他示例

using (D d = new D())  // OK
using (C c = new C(d))
{
    c.UseLimitedResource();
}
Run Code Online (Sandbox Code Playgroud)

这相当于

using (D d = new D())
{
    using (C c = new C(d))
    {
        c.UseLimitedResource();
    }
}
Run Code Online (Sandbox Code Playgroud)


Kei*_*thS 8

我能想到的最相关的例子:

if(someCondition)
   if(someOtherCondition)
      DoSomething();
else
   DoSomethingElse();
Run Code Online (Sandbox Code Playgroud)

哪个ifelse搭配?缩进意味着外部if获取else,但实际上并不是编译器将如何看到它; 该 if将得到else和外if没有.你必须知道(或者在调试模式下看到它的行为)通过检查来确定为什么这段代码可能会失败你的期望.如果你了解Python,它会变得更加混乱; 在这种情况下,您知道缩进定义了代码块,因此您希望它根据缩进进行评估.然而,C#并没有给出关于空白的飞行翻转.

现在,那就是说,我并不特别同意这个"总是使用括号"的规则.它使代码非常垂直嘈杂,降低了快速读取代码的能力.如果声明是:

if(someCondition)
   DoSomething();
Run Code Online (Sandbox Code Playgroud)

......然后应该像这样写.声明"总是使用括号"听起来像"总是用括号括起数学运算".这会把非常简单的陈述a * b + c / d变成((a * b) + (c / d)),引入错过密码的可能性(许多编码员的祸根),以及为什么?操作顺序众所周知且执行得很好,因此括号是多余的.您只能使用括号来强制执行与通常应用的操作不同的操作顺序:a * (b+c) / d例如.块状括号类似; 使用它们来定义你想要做的事情,如果它与默认值不同,并且不是"明显的"(主观的,但通常很常识).

  • @AlexBrown ......这正是我的观点.OP中所述的规则是"总是使用括号,即使是单行",我之所以不同意这一点.Brackets*会帮助第一个代码示例,因为代码的行为不会像它缩进的那样; 你必须使用括号将`else`与第一个`if`配对而不是第二个.请删除downvote. (3认同)

Pur*_*ret 5

通过回答没有人明确说明我习惯的那种练习,讲述你的代码的故事:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0)
    {
        j++;
    }
}
Run Code Online (Sandbox Code Playgroud)

变为:

int j = 0;
for (int i = 0 ; i < 100 ; ++i)
{
    if (i % 2 == 0) j++;
}
Run Code Online (Sandbox Code Playgroud)

j++同一行的,如果要发信号给别人,"我只希望这一块永远增加j".coursethis是唯一有价值的,如果该行是尽可能简单,因为把一个断点在这里,因为周边提到,不会是非常有用的.

事实上,我刚刚遇到了部分Twitter Storm API,它在java中有这种"类型"代码,这里是执行代码的relvant片段,在本幻灯片的第43页上:

...
Integer Count = counts.get(word);
if (Count=null) count=0;
count++
...
Run Code Online (Sandbox Code Playgroud)

for循环块有两个东西,所以我不会内联该代码.即从不:

int j = 0;
for (int i = 0 ; i < 100 ; ++i) if (i % 2 == 0) j++;
Run Code Online (Sandbox Code Playgroud)

这很可怕,我甚至不知道它是否有效(按预期); 不要这样做.新的线条和大括号有助于区分单独但相关的代码片段,就像散文中的逗号或分号一样.上面的块是一个很长的句子,有一些条款和一些其他语句从不打破或暂停来区分不同的部分.

如果你真的想要给别人发电报它是一个单线工作,使用三元运算符或?:形式:

for (int i = 0 ; i < 100 ; ++i) (i%2 ? 0 : >0) j++;
Run Code Online (Sandbox Code Playgroud)

但这是代码高尔夫,我认为不是很好的练习(我不清楚我是否应该把j ++放在:或不是一边).NB我之前没有在C++中运行三元运算符,我不知道这是否有效,但确实存在.

简而言之:

想象一下你的读者(即维护代码的人)如何解释你的故事(代码).让它们尽可能清楚.如果你知道新手编码器/学生正在维护这个,甚至{}可能尽可能多地留下,这样他们就不会感到困惑.

  • 同时在一行中放置多个表达式会使调试代码变得更加困难.你如何在第二次表达中放置断点? (2认同)

Kon*_*ski 5

因为当你没有两个陈述时{},很容易错过一个问题.我们假设代码看起来像这样.

int error = 0;
enum hash_type hash = SHA256;
struct hash_value *hash_result = hash_allocate();

if ((err = prepare_hash(hash, &hash_result))) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &client_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &server_random)) != 0)
    goto fail;
if ((err = hash_update(&hash_result, &exchange_params)) != 0)
    goto fail;
    goto fail;
if ((err = hash_finish(hash)) != 0)
    goto fail;

error = do_important_stuff_with(hash);

fail:
hash_free(hash);
return error;
Run Code Online (Sandbox Code Playgroud)

看起来很好.它的问题很容易被遗漏,特别是当包含代码的函数更大时.问题是goto fail无条件地运行.你很容易想象这是多么令人沮丧(让你问为什么最后hash_update总是失败,毕竟一切看起来都很好hash_update).

然而,这并不意味着我要在{}任何地方添加(在我看来,{}到处看都很烦人).虽然它可能会导致问题,但它从来没有为我自己的项目做过,因为我的个人编码风格禁止条件而{}不是它们不在同一条线上(是的,我同意我的编码风格是非常规的,但我喜欢它,而我在为其他项目做贡献时使用项目的代码样式).这使得以下代码很好.

if (something) goto fail;
Run Code Online (Sandbox Code Playgroud)

但不是以下一个.

if (something)
    goto fail;
Run Code Online (Sandbox Code Playgroud)