自从马斯克入主Twitter之后,Twitter自身问题的热度似乎有霸榜的趋势,各种吐槽,各种抱怨,各种摆烂,各种矛盾都像礼花似的喷射向天空,只可惜带来的不是绚烂的风景,而是乌云和阴影。
Twitter到底怎么了?
◇ 2015年8月
我曾到访位于旧金山的Twitter办公大楼
遥想当年,Twitter作为开启新社交生活的互利网巨头,从早期使用Ruby On Rails快速完成网站的打造,到后来随着流量暴增产生性能和并发瓶颈后改用Scala为主要语言对其改写,它在互联网企业中一直扮演引领技术风气之先的角色,Twitter的架构经验也成为人们竞相学习的蓝本。
为何到了今时今日,Twitter的技术领先形象却产生大反转,难道是Twitter工程师们突然智商下线了吗?实际上,不止Twitter如此,哪家IT科技公司又不会面临各种软件系统带来的遗留问题呢?——这实际上是软件熵在发生催化作用。
软件熵起名于现实世界中熵的主要特点:即对混沌的度量,要么保持不变,要么随时间增长。换种方式来说,软件熵是对关于修改软件系统而产生的内在不稳定性的度量。
时间是一把杀猪刀,一位风华正茂的翩翩少年,会因为岁月的增长,沦落为腆着小肚腩顶着半秃头的油腻男,更何况软件乎?
据报道,通过分析此次Twitter的问题,认为是公司的一名可靠性工程师执行了一次错误的配置变更,破坏了Twitter API的正常运作,并因此在公司内部引发了连锁反应,导致Twitter多款内部工具和面向公众API全部瘫痪。
这就就是典型的“牵一发而动全身”了。表明看来,这是一个非常低级的错误,背后却揭露了长期维护的大规模软件系统常常存在的真相:看起来表面风光,实际上内心混乱,不知什么时候就会暴雷。像极了某圈光环下的李某吴某郑某们!
该如何补救或降低软件熵带来的影响呢?以我之浅见,大约需要把握三个方面的实践。
01 架构边界
我一直强调边界的控制力带来的安全性。软件架构的边界和一个国家疆域的边界一样重要。从某种角度看,架构师的主要作用就是厘定边界,并制订原则且推行最佳实践以守住边界。
架构的边界,还是工作的边界,也是运维的边界,它是获得清晰架构的基础。
确定并守住边界并非闭关自守,宣称彼此之间老死不相往来。既然是边界,就必然存在在防守的同时还要有适度的开放。开放的出口统统可称之为“开放API”。开放API时,就意味着你定义了一个协议,需要完成对外部请求的履约,正如海关允许外部的人或物入境,却必须做必要的入关检查。
一旦开放API,就会产生内外部之间的依赖,也必然产生多个团队之间的协作。管理好依赖,并理顺各种依赖关系,是设计人员和运维人员的职责:
- 设计人员的职责:尽量减少不必要的依赖,并避免或尽量避免出现循环依赖;确保开放API的容错性、可降级性,定义其服务等级协议(SLA)等
- 运维人员的职责:追踪和监控调用链接,及时发现或预测各个开放API的压力瓶颈,一旦出现故障,需要及时告警并及时处理等
虽然每一位架构师可能都清楚架构边界的重要性,事实却是一个边界清晰的软件架构,往往会随着时间的推移,慢慢变得混乱,最后变成让人迷路的依赖网:
如此,也就产生了代码的腐化。该如何避免呢?
02 代码评审
频繁开展代码评审是避免代码腐化的最有效武器。然而,总是有许多团队和个人推说没有时间,而不开展代码评审。这一理由,就和一个人说我因为太忙没时间洗澡一样的荒唐,可这确实软件行业悲哀的事实!
文末做一个调查,了解一下贵公司或你所在的团队要做代码评审吗?
虽然现在看不到结果,我想,做代码评审的比例应该不会太高。
这里所说的代码评审,可以有三种形式:
- 融入到CI/CD流水线的代码静态扫描
- 团队定期召开的内部代码评审(可以是每天或每周)
- 以提交流程方式运作的代码评审
如下图所示,是我为某客户制订的代码评审流程:
代码评审并非批判会,更不是评估工作绩效的工具。如果你将代码评审作为对个人的考核工具,你可能就走在错误的道路上,效果可能适得其反。
代码评审的本质是交流,形成开发人员彼此之间的信息共享,同时,也是提升能力的一种有效手段。Linux内核维护人员Andrew Morton就认为:
它能帮我们发现bug,提升代码的质量,有时还能防止将严重的问题带进产品。如核心内核中的漏洞,我在评审的时候就发现过大量这样的漏洞。
它也能让更多的人理解新的代码——评审人和那些关注这个评审的人现在都能更好地为这些新代码提供技术支持。
此外,我希望,代码的作者们知道他们提交的代码会被仔细评审,从而更加认真地对待他们的工作。
要知道,Linux就完全符合“长期维护的大规模软件系统”这一定义,为何他却没有受到软件熵的痛击呢?据有关报道,Linux内核的缺陷率为0.66缺陷/千行代码,低于许多同等规模的商业项目。一个成功秘诀,无疑就是长期不懈开展的代码评审。
03 持续测试
代码评审是静态的,还需要有效地手段了解软件运行的状态。除了提供必要的可观测性之外,建立完整的测试金字塔,形成持续测试,意义重大。
测试金字塔有很多版本,定义了不同类型的测试,上图所示来自Martin Fowler的网站文章,比较准确地概括了主要的测试类型。其中,End-to-end也包括了我们常说的契约测试或API测试。
搭建持续测试的测试保护网需要付出非常大的成本,但对于一个长期维护的大规模软件系统而言,如果能够从一开始就建立这一机制,并保证足够的测试覆盖率,带来的效果越到后面就越能肉眼可见。
当然,自动化测试自身也可认为是长期维护的软件系统,它也存在软件熵。因此,我们需要将测试代码与产品代码等同视之,也需要纳入到代码评审的范围,并做到及时重构,持续不断的提升其代码质量。
结论
回归到Twitter的问题。如果我们能清晰地定义架构边界,并让复杂的依赖关系可视化,也许就能避免前面所述的连锁反应故障。次之,如果在提交代码(包括测试和运维代码)时,能启动代码评审的流程,通过引入更多评审者,也许就能通过更多双慧眼及时发现问题。最后,如果针对代码的修改可以触发测试环境、类生产环境搭建的层层保护网,也许就会因为某一个测试覆盖到该场景而能在上生产环境之前就能发现!
当然,所有的结论都是也许,因为归根结底,这些实践都是由人来推进的,是人就会犯错。这个世界上,大约除了hello world之外,没有哪个软件系统没有bug,不会引起一个故障。
在专业技能与实践方面,不妨对开发人员要求狠一点;而在出现故障的时候,不妨宽容一些,先找出原因,进行合情合理的复盘,尝试改进。管理者需要认识到这三个实践的重要性,马上开始,推行它们,毕竟亡羊补牢犹未晚矣!