是否有人可以挑战鲍勃叔叔,因为他喜欢移除“无用的牙套”?


94

我讨厌引用付费墙内容,但是这段视频恰好显示了我在说什么。恰好在罗伯特·马丁(Robert Martin)的12分钟内看了一下:

一些代码

并说“我最喜欢做的一件事情就是摆脱无用的牙套”,因为他将其变成了这样:

更多代码

很久以前,在遥远的教育中,我被教导不要这样做,因为它可以通过添加另一条缩进的行以使其很容易引入错误,认为该错误是由if不受时间控制的。

为了公平起见,鲍勃叔叔(Bob)将他的Java长方法重构为微小的小函数,我同意这些小函数的可读性要高得多。当他完成更改后,(22.18)看起来像这样:

还有更多代码

我想知道是否应该验证删除括号。我已经熟悉了最佳实践。鲍勃叔叔可以在这一点上受到挑战吗?鲍勃叔叔捍卫了这个主意吗?


6
我投票结束这个题为离题,因为对此的任何答案都将是“否”或“是,这是引文”。
Blrfl 2016年

35
给它几年时间,也许他也将删除无用的换行符,然后“如果(参见(某事))说(某事)”;实际上是一行代码;-)
史蒂夫·杰索普

8
@SteveJessop很高兴听到我提前几年。:D没有换行符,就不会有臭名昭著的bug的风险。根据需要添加/删除括号是额外的开销,但是阅读时会节省更多。
maaartinus

9
Bob大叔让一些好点在清洁代码,第35页。这样做如果一个if块是永远只能一条线,也没关系。如果您需要添加更多行,则应该使用另一个函数将if块减少到一行(函数调用)。如果您坚持这样做,那么括号根本就没有关系-根本没有关系

13
return (page==null) ? "" : includePage(mode,page);如果他要变得那么简洁,那么他应该做...我一直以为没有花哨的风格很酷,直到我开始专业地开发应用程序为止。杂音,可能的拼写错误等。括号一直存在,为您节省了以后需要引入它们的时间和开销。
vaxquis

Answers:


47

可读性不是一件小事。

当括号包含单个方法时,我的想法很复杂。我个人删除它们是为了诸如单行返回语句之类的事情,但是实际上遗忘了大括号确实在我工作的最后一个地方使我们非常痛苦。有人在if语句中添加了一行代码,但未添加必要的花括号,并且由于它是C语言,因此使系统崩溃而没有警告。

我从来没有挑战过那些在那次惨败之后总是使用牙套的人。

因此,我看到了可读性的好处,但是我敏锐地意识到,将括号排除在外可能会产生问题。

我不会为寻找研究或某人发表的意见而烦恼。每个人都有一个(就是这样的观点),并且因为这是一个风格问题,所以一个观点几乎与其他观点一样好。自己考虑问题,评估利弊,并下定自己的想法。如果您工作的商店的编码标准可以解决此问题,请遵循该准则。


7
当压痕消失并产生误导时,我为这个问题感到最近。
安迪

12
因为它是C,没有GCC 6-Wmisleading-indentation
wchargin

2
@CandiedOrange:鲍勃叔叔正在挑战既定的教条。
罗伯特·哈维

1
@RobertHarvey我没说清楚吗?是的,他在挑战教条。他在挑战我的教条。我只是想成为一名好学生,至少要考虑一下。但是鲍伯叔叔是如此的有魅力,让我怀疑我是否正在失去客观性。因此,我恳求在喝古德莱酒之前寻求帮助,找到解毒药。
candied_orange

1
@CandiedOrange:绝对是上下文。那些盲目接受教条的人不会考虑背景。在该规则已失去其实用性并实际上已成为障碍之后,他们仍然在托管语言中仍然使用“仅返回”规则。
罗伯特·哈维

133

您可以在此处此处或涂有自行车棚的任何地方找到数种已发布的促销或拒绝无节制风格的东西

走出自行车棚,还记得2014年的OS X / iOS SSL错误吗?

if ((err = SSLHashSHA1.update(&hashCtx, &serverRandom)) != 0)
    goto fail;
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
    goto fail;
    goto fail;
if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
    goto fail;

是的,是由无括号阻止造成的https://www.imperialviolet.org/2014/02/22/applebug.html

首选项可能取决于花括号的样式。如果我必须写

if (suiteSetup)
{
    includePage(mode, suiteSetup);
}

我可能也倾向于节省空间。但

if (suiteSetup) {
    includePage(mode, suiteSetup);
}

仅使用一个“额外”行。


我知道你没问,但是如果我一个人工作,我是一个异端。如果我删除括号,我更喜欢

if (suiteSetup != null) includePage(mode, suiteSetup);

它不具有相同的视觉问题,因为iOS的SSL风格的bug,但节省了甚至超过了Bob大叔做“不必要”的线条;)如果你已经习惯了读好,并拥有主流的应用在Ruby中(includePage(mode, suiteSetup) unless suiteSetup.nil?)。哦,好吧,只要知道有很多意见。


13
另外,如果您使用} else[if(...)] {,则不会添加任何额外的额外行,因此在整个过程中它总共只会增加一个额外的行。
Random832 '16

12
很高兴您提出iOS SSL错误。从那时起,我越来越发现自己在这种情况下使用牙套。
扎克·利普顿

4
关于“ goto fail”错误,值得注意的是,Bob叔叔的编码风格的其他方面也会阻止它,最重要的是,他强烈偏爱极短的方法。首先,他永远不会容忍79行方法,并且将方法分解为较小的方法(a)导致重复的合并冲突可能永远不会发生,以及(b)方法中的流程控制更简单可能意味着已经生成了针对无法访问的代码的编译器警告,应该可以避免该问题。
朱尔斯

16
“ goto失败”不是由单行代码块引起的,它是由草率的编程,甚至是更草率的代码审查以及甚至没有一次手动执行代码引起的。
gnasher729

35
嗯,缺少括号并没有导致该错误。令人震惊的程序员(并且没有任何有意义的代码审查,测试)做到了。通过解雇负责人来解决问题,而不是束缚其他人的双手!
Lightness Races in Orbit

62

鲍勃叔叔对这种错误有很多防御层,当“总是使用牙套”成为普遍的智慧时,这种错误就不那么普遍了:

  1. 个人对单行条件句的偏爱很高,因此多行条件句会脱颖而出并受到额外的审查。
  2. 当您插入第二行时,该编辑器会自动降低出价。
  3. 他不断运行的一整套完整的单元测试。
  4. 完整的集成测试套件。
  5. 在合并他的代码之前进行的同行评审。
  6. 由连续集成服务器重新运行所有测试。
  7. 产品质量部门进行的测试。

我认为,如果有人向鲍勃叔叔发表了挑战,那么他会很好地反驳上述几点。这是提早发现的最简单的错误之一。


16
我从不担心崩溃,就像我害怕那些无声的失败一样。至少您知道何时发生崩溃。当我看到这些东西时,我的脑后会发麻。它像我看见时一样刺痛while(true);{break;}。如果确实有一个有效的上下文,那将需要我花费一些时间来克服它。
candied_orange

9
我们是否有一种自动化的检查方法,includePage()它不是一个包含多个语句的写得不好的宏?
马丁·约克

51
@LokiAstari是的,它被称为“ Java没有宏”。
svick

11
以上所有这些实际上是“避免使用与“无用括号”政策有关的失败伤害我的方法”,而不是实际上是“使用“无用括号”政策的理由”。您需要的事实(特别是#1)应被视为一种破坏而不是优势。
SJuan76 '16

16
@Jules哦,是的!我在工作中收到或发布的所有产品(至少)具有100%的测试覆盖率,经过同行评审(多次),并且所有业务部门均具有软件质量检查部门。是。我猜您在职业生涯中发现了相同的情况,因为每个企业都有程序员团队而不是单个程序员,并且他们给他们足够的时间来进行他们想做的所有测试。是的,完美地描述了所有工作场所。当我们确定我们的软件始终随附100%的错误时,为什么还要担心添加一些其他错误?
SJuan76 '16

31

在大多数情况下,这是个人喜好,但是需要考虑一些事项。

可能的错误

虽然可以说,由于忘记附加括号的错误是罕见的,从我所看到 的是 他们 发生 偶尔(不要忘记著名的IOS转到失败的bug)。因此,我认为这应该是考虑您的代码样式时要考虑的因素(某些工具会警告您误导性缩进,因此它也取决于您的工具链)

有效的代码(即读取像它可能是一个错误)

即使假设你的项目没有从这样的错误苦,读代码的时候,你可能会看到一些代码块,看上去就像他们可能是错误-但不是,采取了一些你的心理周期。

我们从开始:

if (foo)
    bar();

开发人员添加了有用的注释。

if (foo)
    // At this point we know foo is valid.
    bar();

后来,开发人员对其进行了扩展。

if (foo)
    // At this point we know foo is valid.
    // This never fails but is too slow even for debug, so keep disabled.
    // assert(is_valid(foo));
    bar();

或添加一个嵌套块:

if (foo)
    while (i--) {
        bar(i);
        baz(i);
    }

或使用宏:

if (foo)
    SOME_MACRO();

“ ...由于宏可能定义多行代码,因此宏会do {...} while (0)用于多行吗?这应该是因为它在我们的样式指南中,但我最好还是以防万一!”


上面的示例都是有效的代码,但是代码块中的内容越多,您需要阅读的内容越多,以确保没有任何错误。

也许您的代码风格定义了多行块需要大括号(无论如何,即使它们不是代码),但我已经在生产代码中看到了这些注释。当您阅读它时,会有一个小小的疑问,就是最后编辑这些行的人忘了添加括号,有时我觉得需要仔细检查是否正在按预期的方式工作(尤其是在研究该代码区域的错误时)

差异噪声

对单行使用括号的一个实际原因是减少diff噪声

也就是说,更改:

if (foo)
    bar();

至:

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

...导致条件行在更改时显示在差异中,这增加了一些小的但不必要的开销。

  • 这些行在代码审查中显示为已更改,如果您的差异工具是基于单词的,则您可以轻松地看到只改变了花括号,但这需要花费更多的时间检查行是否完全没有更改。
    话虽如此,并非所有工具都支持基于单词的差异,diff(svn,git,hg等)将显示整行,即使使用精美的工具也是如此,有时您可能需要快速浏览一条普通线基于差异的差异,看看有什么变化。
  • 注释工具(如git blame)将显示更改的线,从而使跟踪线的原点更加容易以找到真实的更改。

这些都是很小的,并且取决于您花费多少时间在代码审查或跟踪下,这些时间提交更改的代码行。

在diff中进行额外的行更改会带来更明显的不便,代码更改的可能性更大,这会导致冲突,这些冲突会合并在一起,需要手动解决


对于有独立代码行的代码库,有一个例外{-这不是问题。

差异的噪音,如果你在这个风格写的论点是站不住脚的:

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

但是,这不是一个常见的约定,因此主要是为了提高完整性(不建议项目应使用此样式)


8
我喜欢将diff噪声减少的评论,这是一个明确的优点。
马丁·约克

2
如果只有每个对JSON着迷的人都对差异噪声有所考虑!我在看着你,最后一个逗号规则。
罗曼·斯塔科夫

1
,我没有考虑过{自己的在线风格带来的差异噪声好处。我真的很讨厌那种风格,不喜欢在需要那种风格的项目上工作。也许考虑到这一点,我将不得不用这种方式编写代码,这会减少一些挫败感。代码密度很重要。如果您一次可以在监视器上看到所有功能,则可以更轻松地查看整个内容。我喜欢在函数内单独的代码块之间留空行,以提高可读性(对相关语句进行分组),并且被迫在其他地方浪费空间,使预期的中断变得更弱。
彼得·科德斯

1
@ peter-cordes也不太{喜欢自己的在线风格,仅将其包括在内以确保答案的完整性。至于信任使用的宏do{}while(0),我发现问题在于您几乎可以一直信任它。问题是确实发生了事故,错误可能会因代码审查而漏失……并且可能会引入原本不会的错误。
ideaman42'6

2
如果我们列出了潜在的错误,则注释掉个别行的功能是另一回事。我正在跟踪一个错误,该错误是由某人盲目注释单行if语句的正文引起的。然后,以下语句是有条件执行的,而不是无条件执行的。
Eldritch Cheese

15

多年前,我被带去调试一些C代码。该错误很难被发现,但是最终归结为如下语句:

if (debug)
   foo (4);

结果发现,编写它的人已将其定义foo为宏。包含行代码的宏。当然,这两行中只有第一行受制于if。(因此第二行是无条件执行的。)

这对于C及其预处理器可能是绝对唯一的-在编译之前进行替换-但我从未忘记过它。那种事情在您身上留下了印记,为什么不安全地使用它-尤其是如果您使用多种语言和工具链,并且不能确定在其他地方不可能有这种恶作剧?

现在,我显然与其他所有人不同地缩进和使用花括号。对于一行if,我会做:

if (debug) { foo (4); }

因此不需要任何额外的行来包含花括号。


6
在那种情况下,谁写那个宏是谁的错。
gnasher729,2016年

6
正确编写C预处理程序宏可能并不直观,但是可以做到。正如gnasher729所指出的,它应该做到。您的示例就是为什么包含多个语句的任何宏都必须在do { ... } while(0)循环中包含其主体的原因。这不仅可以确保始终通过包围if()语句来正确对待它,而且可以确保正确地吞入了语句末尾的分号。谁不知道这样的简单规则,就不应该编写预处理程序宏。在呼叫站点进行防御是错误的防御位置。
cmaster

18
@cmaster:是的,但是其他人如何编写宏(或其他语言功能)是我无法控制的。大括号的使用方法由我控制。我绝对不是说这是包含括号的铁定理由,但是我可以做些事情来保护自己免受他人的错误的影响,所以我做到了。
韦恩

@ gnasher729,谁在乎别人的错吗?如果您正在执行代码审查并遵循一些合理的代码标准,那么您的团队也很可能犯错。如果它到达了用户,他们将责怪该组织发布错误的软件。
ideaman42

1
谁提出宏是谁的错。
尼姆

14

允许“鲍勃叔叔”发表意见,允许您发表意见。无需挑战他。

如果您想寻求权威,请选择克里斯·拉特纳(Chris Lattner)。在Swift中,if语句失去了括号,但始终带有花括号。没有讨论,这是语言的一部分。因此,如果“鲍勃叔叔”开始删除花括号,则代码将停止编译。

检查别人的代码并“摆脱无用的括号”是一个坏习惯。仅在需要检查代码以及不必要地产生冲突时才引起额外的工作。也许“鲍勃叔叔”是一个非常出色的程序员,他不需要代码审查?我浪费了一个星期的时间,因为一位顶级程序员在没有代码审查的情况下将“ if(p!= NULL)”更改为“ if(!p)”,这被隐藏在最糟糕的地方。

这主要是一场无害的风格辩论。花括号的优点是您可以在不添加花括号的情况下插入另一行代码。就像日志记录语句或注释(如果后面紧跟注释,则太糟糕了)。同一行上的语句似乎有实际的缺点,即许多调试器都存在问题。但是随便你怎么做。


1
我猜这是关于“挑战”的,因为UB(原文如此!)将他的观点陈述为事实-至少当我听他讲话时,我的观点如此。
vaxquis

3
说“做任何你喜欢的事”实际上是同意鲍勃叔叔的观点,因为那是他认为“没关系”的意思。在许多语言中这不是问题。我以前曾在所有问题中都想到过,应该始终使用大括号,而没有人挑战这一问题。现在是Bob叔叔在挑战它,我想知道他是否正在摆脱它,是因为他以如此高度重构的微小方法工作,还是因为他吃饱了。由于@PaulDraper在回答中提到的各种错误,我没有想到它是无害的。
candied_orange

回复总是:语法噪音分散注意力,对关闭括号额外的“空白”的线在该线不应该被分开的段落增加了视觉分离,进一步阻碍了可读性。在您提到的简洁小块中,这一点尤其重要。
JDługosz

9

我不删除括号的原因是:

  1. 减少决策疲劳。如果您始终使用牙套,则无需决定是否需要牙套。

  2. 减少开发负担:即使您最终努力将所有多行逻辑提取到方法中,也不得不将无括号的转换为有括号的(如果添加逻辑),这是开发的烦人点。因此,需要删除花括号,并在需要更多代码时再次添加花括号。很小,但是很烦。


0

一位年轻的同事曾说过,我们认为多余和多余的牙套实际上对他有帮助。我不记得确切的原因,但是如果它允许他更快地编写更好的代码,那就是保留它们的唯一原因。

FWIW,我们商定了一个折衷的是将它们放入一个行不做出这样短的先决条件读/分心给我。例如

if (!param) { throw invalid_argument (blahblah); }
if (n < 2) { return 0; }
// real code for function starts here...

我还指出,这些介绍性先决条件通常是身体的控制流语句(例如a return),因此担心添加第二条在相同条件下的语句而忘记写括号的想法很无聊。这种情况下的第二条语句无论如何都是死代码,没有意义。

我怀疑阅读问题的流利性是由于该人的大脑解析器被连接起来以将大括号作为条件语句的一部分而引起的。那不是C ++语法的准确表示,但是可能是首先学习某些其他语言的副作用。


1
鲍勃叔叔可能认为没有它们,他可以更快地编写更好的代码。
djechlin

2
我和其他人也能熟练使用C ++。
JDługosz

1
“如果它能让他更快地编写更好的代码,那就是保留它们的唯一原因。” ++我有一个小儿子也这么说,因此,我们保留了它们。
RubberDuck

...因此,这就是独自按照鲍勃叔叔想要的方式这样做的原因。
djechlin

-1

刚刚看完该视频,我注意到消除花括号可以使您更轻松地了解仍需要重构代码的位置。如果需要花括号,您的代码可能会更干净一些。

话虽如此,当您在团队中时,消除括号可能有一天会导致意外行为。我说保留它们,当事情出错时,您会为自己省去很多麻烦。


在先前的10个答案中所提出和解释的观点上,这似乎没有提供任何实质性内容
gna

-3

有人告诉我不要这样做,因为它可以通过添加另一个缩进的行以使其容易地引入错误,认为该错误是由if来控制的。

如果您的代码已经过良好的单元测试,则这种“错误”将在脸上爆炸。

我遵循的做法是避免使用任何无用且丑陋的括号来清理代码。


9
当然,反之亦然:如果您的代码没有错误,那么您不需要任何单元测试。现实情况是,我们生活在一个不完美的世界中,其中有时会出现代码错误,有时还会出现测试错误。(以我的经验,后者实际上更常见。)因此,尽管测试肯定会降低错误的风险,但这并不是寻找其他方法来再次提高风险的借口。
ruakh

3
补充一下ruakh的评论:不可能100%对代码进行单元测试。
BЈовић

请来看我的代码;)在练习TDD时,总是可以非常迅速地显示这种错误。
Mik378

@ Mik378事实并非如此。关键是,如果您使用牙套,您甚至不必担心。
GoatInTheMachine
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.