为什么"简单"如此复杂

为什么"简单"如此复杂

软件工程中”简单”的复杂性

简单可能比复杂更难:你必须努力使思维清晰才能简化它。但最终这一切都是值得的,因为它能使你翻越高山。
—— Steve Jobs

我相信有两种设计软件的方式:一种是使软件足够简单而明显没有缺陷;另一种是使它如此的复杂,以至于没有明显的(可被轻易发现的)缺陷。

—— Tony
Hoare(译者注:英国计算机科学家、图灵奖得主、快速排序算法的发明人、哲学家就餐问题的提出者……)

简单,软件工程的圣杯。业界的前辈一直在驱策我们去追求它。

(软件设计)是一门手艺……相对”复杂”而言,它赋予”简单”更高的价值
—— Barbara Liskov(译者注:2008年图灵奖得主)

如果我们想构建好的系统,就需要构建简单的系统 —— Rich
Hickey(译者注:Clojure语言的发明人)

成功地开发复杂软件的唯一方法是降低它的整体复杂性,使用简单的部件来构建它。——
Eric S. Raymond (译者注:著名黑客、开源运动领导者之一)

简单是可靠的先决条件。 —— Edsger Dijkstra
(译者注:结构化程序设计的早期贡献者、一大批算法的发明人、哲学家就餐问题的解决者……)

可靠性的代价是对终极简单性的不谢追求 —— Tony Hoare

并且,与圣杯一样,简单性已被证明是极难获得的。主要有这样一些原因:

  • 软件有其本质上的复杂性。软件的不同元素以非线性的方式互相交互。相对线性系统而言,这提升了整体的复杂性。
  • 你不总是能控制必须使用的软件(例如第三方软件库、由公司内其他小组开发的库、遗留系统等。)
  • 总是存在修改的压力。
  • 不同的需求和利益之间常常会有冲突。
  • 整体复杂性相对线性系统而言增加了很多。

非技术因素

一个程序员必须控制的复杂性很多是由他必须与之打交道的人或组织所无理地强加于他的。——
Fred Brooks (译者注:《人月神话》作者)

当我们无论何时考察一个软件时,我们的思维都必须超越技术层面。这(非技术因素)是软件存在的理由,而且它们与代码并无关系。

这些事物或人包括:

  • 用户
  • 业务与利益相关方
  • 工程师、(UI)设计师、业主的需要和利益
  • 财务约束
  • 在很多场合,这些错综复杂的问题都是由不同人的利益和政治因素驱动的。也许我们不喜欢这些,但它们是真实存在的影响软件开发的因素。忽略它们的影响及其复杂性只会将我们自己至于危险中。

复杂性是无法避免的

  • 长期来看所有的程序都会变成”洛可可”风格——然后是一片废墟(rubble)。——Alan
    Perlis (译者注:1966年首届图灵奖获得者。)

(译者注:洛可可(rococo)是18世纪流行于欧洲的一种艺术风格。在装饰艺术上体现为大量使用繁复的、变化丰富的曲线。这里指程序变得越来越复杂,同时与后面的rubble读音上相似。)

在任何有意义的软件程序中,复杂性都是无法避免的。软件系统必须演化,否则就会过时。随着软件的演进,新的功能或修改原有的功能都会增加复杂性。因此,事实证明,将复杂性引入软件是我们的利益所在。

简单性不可能来自简单的思维

当软件业迷失于复杂性之中时,如果有一种方法能保证带来高质量的软件,无疑会受人青睐。很多人曾经宣称过找到了某种带有魔力的技术或方法。现在让我们来考察其中的一些:

  • 静态类型
  • 框架

静态类型将解决所有问题

曾经大量的宣传将静态类型奉为软件质量的救星。例如在Elm(一种编程语言)的网站上写道:

与手写的Javascript不同,Elm代码在实践中不会产生运行时异常。Elm使用类型推理机制在编译阶段就能发现问题并给出友好的提示。通过这种方式,错误永远不会出现在你的用户面前。

真是一个可爱的声明!如果它是真的话。如果正确使用静态类型就能解决我们的问题,那真是太迷人了。如果这一切是真的话,那么使用像Elm这样的语言就是每一个软件开发者的道德义务。其效应将是非常显著的、并也无疑是及其重要的。但遗憾的是,事实并非如此。

痴迷于静态类型的人试图使我们相信”只要妥当地使用类型系统,程序就不会出错”。这虽然听上去令人印象深刻,但实际只是句空话。静态类型检查是对程序的运营态行为的编译时抽象,因此它必然只是部分有效,并且是不完备的。这意味着程序依然会因为无法进行类型检查的属性而出错,同时某些程序虽然不会出错,但它们也无法进行类型检查。
—— 《程序设计语言的冷战的终结》(Erik Meijer, Peter Drayton)

事实上,正如Meijer和Drayton指出的,对于静态类型的过分强调也可能造成问题,因为这种强调本身会引入新的复杂性:

如同”幽灵类型(phantom types)”和”摇摆类型(wobbly
types)”所描述的,使得静态类型更加完备的冲动可能使类型系统变得过分复杂和奇异。这就好像用链条在脚上拴个铁球去跑马拉松,然后欢呼自己差点就跑完了,而实际不到1英里就退出了。

关于Bug密度的数据显示他们的结论是正确的。相比静态类型而言,简单性与缺陷密度有更大的相关性。

上述研究的作者Dan
Lebrero指出:”数据显示,没有证据表明静态/动态类型对缺陷密度有影响,但是数据显示出……在专注简单性的语言和不关注简单性的语言间有巨大的差异。”

你需要使用正确的框架

编程语言是低层次的,用它编写的程序则需要关注与语言本身无关的东西。——
Alan Perlis

软件业一直致力于提供能使编码更容易、更可靠的抽象层。我们今天可以不用机器语言来写程序就是这一努力的证明。当我们消除了很多偶然复杂性以后,系统固有的核心复杂性却始终存在。并且我们构建的抽象还带来了更多的问题。

Joel
Spolsky的这个评论早已为众人所知:”所有非平凡的抽象都在某种程度上有所疏漏。”(原文:All
non-trivial abstractions, to some degree, are
leaky.”)他的意思是所有的抽象都会在某些方面无法掌握本来想要简化的复杂性,复杂性依然从抽象的裂缝中逃逸出来。

我们可以在React
JS上看到这个理论的最近例证。React的一个主要目的是抽象掉DOM。但是React为需要直接操作DOM的场合提供了”refs”。当然,通过这种方式你可以理解如何与DOM交互。但这也意味着抽象失败了。事实上,必须理解如何绕过React以便与DOM交互,这一点导致了额外的复杂性。

同时,还有一些复杂性从抽象中产生。抽象来自隐藏实现细节的必要性,同时,也对我们所能做到的事情添加了限制。这就是为什么React需要”refs”,因为有些事它做不到。抽象本身可能具有内在的复杂性。

没有银弹

上面的评论可能会招来一些充满宗教热情的争论,这并非我的本意。我的目的是要指出:

无论是在技术上还是在管理上,不存在一种新发明,单独使用就能使软件的生产率、可靠性和简洁性获得数量级的提升。——
Fred Brooks

但是,与但丁的《神曲》不同,你还无需抛弃所有的希望。认识到不存在构建完美软件的神秘魔法有助于将你从魔法思维的王国中拯救出来。只有从这样的奇幻世界中出来你才能应对软件业的现实。于此同时你也将认识到,其实一切并非只关于软件。

其所关更大:它是人、是人际关系、是过程、是我们所处的环境、是现实也是历史、它持续数代又是当下,换句话说……这相关于系统。

如果没有银弹,我们能做什么呢?我们可以接受这些:

总有一些复杂性需要我们去把握;
我们无法写出完美的软件——接受这个现实吧;

  • 总有妥协和不完美;
  • 杰作需要时间;
  • 我们必须处理人的因素(偏好、政治、不同的思维方式,等等);

……设计是最好慢慢地、小心为之的事情。—— Alan
Kay(译者注:Smalltalk的最初设计者、OOP和GUI的先驱。)

是什么造就”简单”?

(软件的简单性)与从复杂的自然现象中概括出简单的物理定理需要同样的技能、专注、洞察力,甚至灵感。它还需要接受由物理、逻辑和技术的约束条件所限制的目标,同时当互相冲突的目标无法达成时接受必要的折衷。——
Tony Hoare

造就”简单”的因素不容易确定。部分是因为”简单”有一定的主观性,部分是因为对简单的理解依赖于经验。对训练有素的数学家来说简单而明显的东西对生物学家来说就并非如此,——反之亦然。这个留给我们的充其量是对”简单”的模糊理解,如同Potter大法官对色情作品的著名定义:”当我看到的时候我就知道了。”

即便如此,还是有一些有助于写出简单的程序的方法:

一旦懂得了系统的基础原则,理解和解决问题就有了把握;

  • 使用众所周知的文化符号的系统比较容易理解;
  • 保持设计的一致性;
  • 充分利用语义;
  • 维持对灵活性的需求和保持系统的边界之间的平衡;
  • 尽可能限定复杂性;
  • 识别并消除不必要的复杂性;

抵御不必要的复杂性

虽然有一些方式使个体开发者能够避免复杂性,但是在组织层面的努力有更大的影响。这里是一些组织能做的事情:

欲速则不达

当项目接近尾声时,总会要仓促地加入一些新功能。这样的赶工简直是发疯,因为它总会将项目引入歧途。——
Tony Hoare

力争使销售、业务部门和产品部门理解到软件开发——开发好的软件——需要时间。

  • 留出时间来设计;
  • 留出时间来实好的编码习惯;
  • 留出时间来重构;

所有的牺牲都有代价。这些代价会随着时间的推移而累加。

避免最新技术的陷阱

软件开发是”时尚产业”。当程序员努力学习方法论、编程语言和框架,以最终能正确行事时,一味追求最新、最强会导致混乱。深思熟虑,并且不盲目跟随随流,有助于在较长时间内保持代码的健康。

经受过考验比”酷”要好得多。

跟随标准流程

程序员(优秀程度)不由他们的聪明和逻辑性来衡量,而由他们的案例分析的完整度来衡量。——
Alan Perlis

有一些标准的、众所周知的、经过实践证明的措施有助于提升软件质量和简洁性:

  • 代码复审——这是软件团队工具箱中最有力的工具。确保周密的计划、组织和执行你们的代码复审;
  • 设计复审——不仅要复审代码、设计也要复审;
  • 足够的顶层设计——不要坐下来就开始编码。仔细考虑系统该如何设计。遵循设计模式以确保设计始终前后一致;
  • 对系统的运行有整体规划。对一些问题有概要的定义,例如:如何处理异步操作;如何处理容错性;如何处理数据一致性,等等;
  • 确保需求是高质量的——不管采用什么方式;

总结

简单性并不简单。同样重要的是记住这只是手段而不是目的。构建易于使用、易于维护的高质量的软件才是目的。

今天,保持系统简单是人们管理软件的最有效方式。简单性是过程和实践、是一系列原则、指导和哲学。它是整个组织应该提倡和追求的。

原文出处

评论