ACM
BLOG@CACM

那些说代码不重要的人

那些说语言不重要的人

Bertrand Meyer"src=

通常,你会被告知编程语言并不重要。到底什么更重要还不清楚;也许是工具,也许是方法,也许是过程。一个相当普遍的规律是,那些认为语言无关紧要的人只是试图为他们使用糟糕的语言辩护。

让我们回到几周前的苹果漏洞。只有几个星期;世界已经转向了《Heartbleed》,但这并不能成为我们抹去苹果漏洞及其语言设计记忆的理由。

2月底,iphone、ipad和ipod的用户被要求立即升级他们的设备,因为“拥有特权网络位置的攻击者可以捕获或修改受SSL/TLS保护的会话中的数据。"该错误被追踪到以下形式的代码[1]:

如果(error_of_first_kind
goto失败;
如果(error_of_second_kind
goto失败;
如果(error_of_third_kind
goto失败;
如果(error_of_fourth_kind
goto失败;
如果(error_of_fifth_kind
goto失败;
goto失败;
如果(error_of_sixth_kind
goto失败;

The_truly_important_code_handling_non_erroneous_case

换句话说:就是一个重复的行!(额外的行在上面突出显示。)但是过度的去做超出了前面的范围。如果,因此执行联合国有条件的:所有的执行直接到失败标签,这样The_truly_important_code_handling_non_erroneous_case从来没有得到执行。

批评人士把他们的怒火集中在“去”指令上,但这几乎无关紧要。在语言方面,重要的是C/ c++ - java - c#约定,它划分了条件指令、循环和其他类型的复合结构的范围。在这些语言中,这种结构的每一个组成部分在语法上都是一条指令,因此:

  • 如果您希望分支由一个原子指令组成,您可以单独编写该指令,如
    如果(c) a = b;
  • 如果你想要一个指令序列,你可以把它写成复合形式,用漂亮的大括号括起来:
    如果(c) {a = b;x = y;}

尽管在原则上很优雅(毕竟,它来自Algol),但从软件工程的角度来看,这种约定是灾难性的,因为软件工程意味着理解程序的变化。一天,条件或循环的分支有一个原子指令;过了一段时间,维护人员意识到相应的情况需要更复杂的处理,于是添加了一条指令,但没有添加大括号。

正确的语言解决方案是将复合指令的概念作为一个单独的概念去掉,而只是期望复合指令的所有分支都由一个序列组成,该序列可以由多个指令组成,也可以只有一个,或者根本没有。在埃菲尔,你会写

如果c然后
x:=y
结束

如果c然后
一个:=b
x:=y
其他的
u:=v
结束

:=1日至c循环
一个:=b

:=+ 1
结束

my_list作为l循环
l.addx

结束

等等。这种语法还消除了所有弥漫在语言中的杂音,这些杂音保留了C语言上世纪60年代的惯例:在条件周围用括号,在不同行上用分号表示指令;这些小的干扰累积成程序可读性的大障碍。

有了如此现代的语言设计,苹果的漏洞就不会出现。重复的行可以是:

  • 关键字如结束,立即被发现是语法错误。
  • 一种实际的指令,如赋值,它的复制要么不产生任何效果,要么只对分支覆盖的特定情况产生影响,而不是像Apple bug那样灾难性地破坏所有情况。

然而,有些人很难接受语言设计的明显责任。看看丹尼斯·汉密尔顿在ACM风险论坛[2]上发表的题为“去松鼠”的嘲讽评论:

让我惊讶的是,一旦具体的缺陷被披露(实际更改的差异也被发布),讨论就会转移到编码风格和谁的代码更好。我记得在阿丽亚娜501的缺陷上也有类似的干扰,尽管在那种情况下,代码没有任何问题——错误是在不需要的时候运行了它,没有使用新的发射参数进行模拟测试,错误的假设是,如果代码适用于阿丽亚娜4号,它就应该适用于阿丽亚娜5号。

这与代码无关。这与代码无关。这不是马上就要发生的事。它并不是通过不同的方式编写代码来避免引入这个特定的缺陷。

这样的确定!重复错误的陈述("这与代码无关)可能不会像在讨论的代码中重复一个指令那样灾难性,重复并不能使语句正确。当然,“它”指的是代码。考虑到如果代码是不同的,灾难就不会发生,人们需要一些勇气来声明这与代码无关- - - - - -考虑到如果编程语言不同,灾难也不会发生,声明它与编程语言无关,这是同样痛苦的。

当汉密尔顿先生解散时分心我想他已经想到了我当时和Jean-Marc Jézéquel[3]一起发表的分析,该分析详细解释了核心问题是缺乏适当的规范(合同)。那时候,我们也听到了轻蔑的评论;根据一位评论家的说法,编程方面不算数,因为整个事情实际上是一个社会问题:在图卢兹的法国工程师没有与他们在英国的同事进行适当的沟通!这种民间解释的伟大之处在于,它们听起来恰到好处,取悦了人们,因为它们强化了现有的刻板印象,本质上是无法反驳的,就像它们无法被证明一样。他们避免提出重要但令人不安的问题:团队是否使用了正确的编程语言、正确的规范方法(如我们所建议的契约)和适当的工具?在阿丽亚娜-5和苹果的案件中,他们都不是。

如果你想被认为是礼貌的,你不应该指出,使用为PDP-8或其他过时的机器设计的编程语言是一场灾难的邀请。人们使用的编程语言越糟糕,他们越知道它很糟糕(即使他们不承认),当你指出它确实很糟糕时,他们就会越感到震惊。这就好像你说了他们的体重或脸颊上的粉刺一样。这样的反应并没有使评论的真实性降低。当技术选择不仅仅是技术争论的问题,而且会对社会产生灾难性的后果时,表达愤怒尤其不合适。

针对语言批评,通常的借口是,更好的工具、更好的质量控制(1997年阿丽亚娜5号调查委员会的主要建议)、更好的方法也可以避免这个问题。实际上,在comp.risks讨论中,包括Hamilton对代码[2]的否定在内的许多其他评论都指向这个方向,例如,注意到静态分析器可以检测到代码重复和不可访问的指令。这些观察都是正确的,但对编程语言的作用和编码问题没有任何改变。从软件和其他工业灾难的研究中得到的基本教训之一 - - - - - -例如,南希·莱韦森的研究表明,灾难是由多种原因综合造成的。这一性质实际上很容易理解:由单一原因引起的灾难极有可能被避免。考虑Amazon事务处理中一个灾难性缺陷的假设例子。从各种来源来看,亚马逊每秒处理大约300个交易。现在让我们假设三个独立的因素,每个发生的概率是千分之一(103),这可能导致失败。然后:

  • 其中一个因素不可能单独导致失败:这意味着它会导致事务在大约3秒后失败,甚至在最琐碎的单元测试中也会被捕获。除了开发者,没人会知道。
  • 如果其中两个因素共同导致失败,那么它们将在每百万次事务中发生,也就是说大约每小时发生一次。任何合理的测试都会在发布版本部署之前发现问题。
  • 如果这三个因素都需要,那么概率是109这意味着大约每年都会发生一次故障。只有在这种情况下,一个真正的问题才会存在:一个在很长一段时间内没有被发现的缺陷,在此期间一切似乎都很正常,直到灾难降临。

这些观察结果解释了为什么灾难的事后检查总是指向一个看似不可能的不幸环境的组合。大公去了萨拉热窝而且他坚持要看伤员而且有人忘了告诉司机们绕过宣布行程的谨慎决定而且车队停滞不前而且刺客看到了而且他正中弗兰兹·费迪南德的脖子而且在许多国家都有民族主义的怨恨情绪而且联盟制度要求国家宣战。工业事故也是如此。苹果漏洞也是如此:显然,没有良好的代码审查,没有应用静态分析工具,也没有良好的管理;而且,很明显,糟糕的编程语言会把无辜的错误吹成全球性的灾难。

在软件工程圈一次又一次听到的公认的智慧就到此为止了,代码不重要,语法不重要,拼写错误会马上被发现,我们应该关心的是过程或敏捷性、需求或其他一些比编程更值得尊重的冠冕堂皇的问题。代码?编程语言吗?我们几年前就没处理好吗?我记得类似的干扰。

有一个积极的结论。而且“在实践中产生灾难的必要原因的本质(用概率的术语来说,是乘法的本质):摆脱它就足够了一个的操作数的而且为了伪造结果,从而避免灾难。当人们告诉你代码不重要或者语言不重要时,你只需要理解注释的真正含义。”我为我所使用的编程语言和技术感到羞愧,但我不想承认这一点,所以我宁愿把问题归咎于世界其他地方,并做出正确的推论:使用一种好的编程语言。

参考文献

[1]保罗Duckline:剖析“即将失败”-苹果的SSL漏洞解释,外加OS X非官方补丁!,裸安全博客(Sophos), 2014年2月24日,可用在这里

[2] Dennis E. Hamilton: Goto Squirrel, ACM风险论坛,2014年2月28日,可用在这里

[3] Jean-Marc Jézéquel和Bertrand Meyer:契约设计:Ariane的经验教训,在电脑(ei), vol. 30, no. 5。1997年1月1日,第129-130页,网上可查在这里并且,随着读者的反应在这里

[4]http://en.wikipedia.org/wiki/Archduke_Franz_Ferdinand_of_Austria#Assassination


评论


匿名

在斯坦尼斯瓦夫·莱姆(Stanislaw Lem)的《花粉热》(Hay Fever)一书中,有一个很好的例子说明了迈耶的后向概率概念:他问,从几公里高的地方把一滴水瞄准,让它击中他家花园里桌子表面的一颗钉子的概率是多少——当然,这种概率是零。然而,在下雨的头几秒钟,整张桌子都湿了。


帕特里斯Bremond-Gregoire

虽然我同意代码确实很重要,而且if语句的不同形式可以避免这种错误,但在我看来,有一种更简单的方法可以避免这种和类似的错误:注意编译器的警告。任何像样的编译器都会警告说,第二次到达下面的代码是不可访问的,注意编译器警告会提醒程序员这个错误。


比尔驳船

这里提出的论点是有效的,而且布局得很好,但是在项目中选择一种编程语言从来不是基于该语言的优点。这些决策更多地是从商业角度(即金钱)做出的。什么东西用起来又便宜!虽然这一点很简单,但这个决定是一个复杂的决定。诸如最熟悉的语言(即大多数开发者知道什么)等因素。我当前写的代码库是什么(如果有的话)。所需工具的成本是多少?


马塞尔研究

据我所知,“不再”如此“天真”到相信技术解决方案被视为成熟的标志。在许多组织中,这当然是晋升到更高职位的必要“资格”,所以这一地位被强制执行,证据见鬼去吧。

我也采用了这种集体思维,直到我发现在很多情况下,技术解决方案才是正确的答案,再多的软技能或组织变革也无法让你达到那个目标。当然,这些因素也很重要,这不是一个非此即彼的情况,而是更类似于航空安全的“瑞士奶酪洞”模型:http://en.wikipedia.org/wiki/Swiss_cheese_model


克雷格·沃德

当然,代码和语言都很重要。通常的问题是编程语言和项目的匹配做得不好,而不是所选择的语言不好。如果你选择一种较老的语言,比如C语言(因为,例如,这是你仅有的团队知道的),你需要加入额外的步骤来防止犯苹果团队犯过的那种错误。不要怪工具,怪工具使用者。

选择“一种好的编程语言”比暗示的要困难得多,因为什么构成了一种好的语言仍然是一个悬而未决的问题。我的感觉是,“划定条件指示范围的惯例”不是最相关的标准。任何命令式语言,比如C语言或它的后继语言,都是有问题的。可能需要的是从这些语言转向函数语言。但即使是函数式语言也可能设计得很糟糕(例如违反了文章的范围标准),并不能帮助防止苹果团队犯的那种错误。


伊恩Joyner

我在某种程度上同意Patrice的观点,即程序员应该注意编译器警告,但是编译器警告只会出现在一种没有很好定义的语言中——一种定义良好的语言不需要警告。为什么会有警告?因为在定义了语言之后,会注意到某些模式可能表明程序员出错了,但不一定。这并不一定是一个问题,因为它表明某些东西定义不清楚。

简而言之,警告不能弥补定义良好的语言,说程序员应该注意警告就等于说过程和方法比语言更重要。按照这种逻辑,我们可以回到汇编语言,并注意汇编程序的警告。

从一个稍微不同的角度来看,测试驱动开发(TDD)也认为过程和方法更重要。我最近在网上发表了一篇文章,讨论TDD的不足之处。

http://ianjoyner.name/TDD_vs_DBC.html

在某种程度上,编程语言不重要的神话之所以出现,是因为大多数编程语言都不是由语言设计专家设计的,因此需要使用诸如编译器警告、过程和方法等工具来弥补他们专业知识的不足。例如,Plauger和Stroustrup在设计他们的语言之前并不是语言设计专家,从那以后我们就一直生活在陷阱和陷阱中。是的,C语言是对ALGOL的重大倒退,而c++是对Simula的重大倒退。对于那些不是语言设计专家的人来说,设计一门编程语言似乎不是一项令人生畏的任务,但它确实是一项艰巨的任务。
相比之下,尼克劳斯·沃思教授和迈耶教授在设计自己的语言之前都是语言设计专家。是时候放弃对编程语言不专业的态度,与那些愚蠢的争论保持一致,承认语言的重要成就始于ALGOL,通过Pascal、Oberon、Simula、Ada和Eiffel。Meyer和Wirth都从早期语言的错误中吸取了教训。

我们还应该考虑Fred P. Brookss在《神秘的人月》中将软件开发技术描述为要素和意外。干净的语言设计是必要的,过程和方法,而重要的是偶然。在《没有银弹》一书中,Brooks指出,软件工程中唯一产生数量级差异的开发是高级语言。这再次表明了编程语言在开发过程中的重要性,Bertrand Meyer对这种情况的评估是正确的。


Igor Schagaev

亲爱的伯特兰,谢谢你的文章。优秀的洞察力。我们经常关注一些几乎无关紧要的事情,而忽略了重要的一点。


显示所有7评论

登录为完全访问
»忘记密码? »创建ACM Web帐号
Baidu
map