简单之道
已经退居二线的 Go 语言之父 Rob Pike 近日发表了一篇名为“Simplicity”的博文,记述了 2009 年在 Google 内部一次圆桌会议上发表的演讲内容。Pike 老先生在这个时间点发表这篇文章究竟有何深意呢?是对Go语言演进的路线有所不满吗?我们不得而知。不过,这篇文章的内容却是非常值得我们学习,这里我简单翻译一下,供大家参考。
2009 年 5 月,Google 举办了一次内部的“设计巫术(Design Wizardry)”小组讨论会,我有幸与 Jeff Dean、Mike Burrows、Paul Haahr、Alfred Spector 和 Bill Coughran 一起发表了演讲。以下是我演讲内容的文字稿(略作了修改)。尽管一些细节可能已经过时,但演讲的主题依然具有重要意义,而且如今它可能比以往任何时候都更为重要。
简单胜于复杂。
简单的东西更容易理解、搭建、调试和维护,而且最重要的是更易于理解,因为这会带来其他一切好处。让我们来看看 google.com 的网页,它只有一个搜索框,你输入查询并获取结果,这个简洁的设计是 Google 成功的一个关键原因。早期的搜索引擎界面要复杂得多,而现在的搜索引擎产品,要么是模仿我们的设计,要么用户体验很差。
那么,Google 搜索引擎(GWS)又是如何运作的呢?我瞥了一眼 GWS 实例的运行参数列表,里面有成千上万的配置标志和数百个参数,还有一些后端机器的名称、后端配置以及一些特性的启用和禁用。其中大多数参数可能是正确的,但我相信也有一些已经过时或错误的。
所以问题是:一个能设计出 google.com 这样简洁网页的公司,怎么可能同时设计出复杂庞大的 GWS 搜索引擎呢?答案是,GWS 并非从一开始就被“设计”出来,而是通过有机增长逐渐形成的。有机增长的路径非常复杂,每个部分、每次修改看起来都很简单,但综合起来就变得难以维护。
复杂性是乘法效应的。在像 Google 这样由多个组件组装而成的系统中,如果你让一个组件变得复杂,那么其中的一部分复杂性就会反映到其他组件中。这就是失控的复杂性。
复杂性也是普遍存在的。
很多年前,Tom Cargill 从贝尔实验室的研究部门休了一年的假,加入了开发部门。他加入的团队每个子系统的代码都被打印出来,装订成册放在员工办公室的书架上。Tom 发现其中一个子系统基本上是完全冗余的,大部分功能在其他地方已经重新实现过。于是他花了几个月的时间删除了那个子系统,删掉了 1.5 万行代码,并从所有人的书架上移走了一整本厚厚的代码目录。这样简化了整个系统,减少了代码量、测试工作量和维护工作量。他的同事们都很高兴。
然而,在评估时出现了问题。Tom 得知管理层有一个衡量生产力的指标是代码行数。由于 Tom 在那一年删掉了那么多代码,他的生产力指标直接变成了负数。更糟糕的是,他的团队的整体生产力也变成了负数。他只能沮丧地回到研究部门。
这个故事给他上了一课:复杂性无处不在。简单性没有获得奖励。
你可以对这个故事嗤之以鼻,但我们与此并不相去甚远。谁会因为删除 Google 的代码而得到晋升呢?我们沉浸在自己拥有的庞大复杂代码中,新员工需要花费大量的精力去理解,我们也投入了大量的资源来培训和指导他们适应。我们为能够理解和修改这些代码而感到自豪。
Google 是一个民主的组织,代码对每个人都是透明的,可以查看、修改、完善和增加功能。但是每增加一点东西,复杂性就会增加。引入一个新的库,复杂性就增加了;再加上一个存储封装,复杂性又增加了;在子系统中添加新选项,配置变得更加复杂。如果对核心基础模块(如网络库)进行这样的修改,整个系统的复杂性都会大大增加。
复杂性就这样逐步累积,其代价以几何级数增长。
另一方面,简单需要付出努力——但大部分工作都在前期。设计简单的系统是非常困难的,但一旦设计完成并实施,后续的维护和操作就会更加容易。选择避免复杂性可以使简化系统的好处成倍增加。
举个例子,来看看我们的查询日志系统。虽然它在完美程度上还有待提高,但在设计初期就被定位为——至今仍然是——Google 内部唯一解决特定核心问题的系统。正是由于这个原因,它确保了系统的稳定性、安全性、一致性以及大规模的经济效益。如果每个团队都自行构建日志基础架构,Google 绝对不会达到现在的规模。
然而,这种思路并没有被广泛应用,各个团队仍然频繁地提出建立新的数据存储系统、工作流系统、代码库、基础架构等等。
这种重复建设和快速增长的复杂性正在拖累我们,因为复杂系统的运作效率较低。
Google 有几条重要的工程原则:代码要可读,要可测试,别惹恼 SRE(译注:Site Reliability Engineering,网站可靠性工程),要追求速度。
然而,简单性从未被纳入考虑。但实际上,它比上述任何一条都更为重要。更简单的设计意味着更易于阅读,代码更易于测试,对 SRE 更易于解释和修复问题。
此外,简单的系统运行速度更快。
注意,我强调的是系统设计,而不是代码。有时为了提升性能,确实需要增加代码的复杂性,这可能是无法避免的。然而,复杂的系统从来都不会变得更快,因为调试和交互变得复杂,难以理解。复杂性只会导致低效。
简单性甚至比性能更为重要。复杂性对系统的影响是成倍增加的——增加 2%的复杂度只能换来 2%的性能提升(或者 1%、0.1%等),这完全得不偿失。
等一下,服务器利用率为什么那么低?难道不是因为系统太慢吗?
不是的,利用率低是因为系统过于复杂。我们无法理解系统的性能表现,无论是在单机还是集群环境下。组件之间的交互难以清晰描述。
应用开发人员无法完全理解底层基础架构。
基础架构工程师也无法准确了解网络状况。
很难弄清楚应用程序需要哪些资源,问题层出不穷。
为了弥补这些问题,每个人都在配置中添加各种参数和补丁代码,使得一切变得更加难以调试。
为了确保产品正常运行,我们只能围绕产品建立防护墙,以避免受到外部环境的影响——然而,这只是增加了更多的复杂性。
这是一个死循环。
所以请仔细考虑你正在进行的工作,是否可以将其设计得更简单一些?那个功能真的是必需的吗?通过删除、合并或共享资源,是否可以使整个系统变得更简单?请考虑与其他相关团队进行讨论,设计一个更简单、更统一的架构,避免相互之间的制约。
多研究已有系统的适应性,在此基础上进行改进,而不是从头开始构建。如果发现现有系统无法满足需求,也许问题出在你对需求的定义,而不是系统本身。
如果确实需要开发新的组件,请确保它可以被推广和重用,不仅仅为本地团队提供服务。
构建复杂系统很容易。为了赶进度,编码比重新设计更简单、更快。但是技术债务会不断积累,长期来看必然会失败。
我们的代码库比一年前增加了 50%。再过一年呢?五年后呢?
如果不能控制复杂性,总有一天不仅仅是利用率低的简单警告。系统会变得过于复杂、运行缓慢,最终导致彻底崩溃。那将是一场“全面崩溃”的灾难。
评论