可牺牲的架构

jopen 9年前

        英文原文:SacrificialArchitecture

        你坐在会议室里,凝视着你的团队在过去数年一直维护的代码。你已经做出了决定,你现在能够做的、最好的选择就是扔掉所有的代码,重建一个全新的架构。对于这些命中注定的代码、你投入其中的时间、此刻之前所做的那些决定,你该作何感想呢?

        对于很多人而言,扔掉代码库是失败的象征,鉴于软件开发固有的探索性质而言是可理解的,但是,这仍然是失败。

        不过,你现在能够编写的最好代码常常是你在数年之后要抛弃的代码。

可牺牲的架构

        我们经常把伟大的代码视作永久的软件。作为一名编辑,我写这篇文章时的身份可追溯到 1980 年。对于软件架构是如何减轻某种永久做了很多思考。然而你的成功可以建立在代码之上,因为被送到了 /dev/null。

        想一下网络上最成功的大型商务网站之一的、eBay 的故事。它在 1995 年用了一周时间,从一堆 perl 脚本开始。在 1997 年,它推翻一切,在当时的 windows 工具之上,用 C++ 编写的系统取而代之。然后到了 2002 年,应用程序用 Java 重写了。早期的版本因为它们被取代了就成为了错误吗?很难这样说。eBay 目前仍然是 web 上最伟大的一个成功,但是成功的大部分因素是建立在 90 年代被抛弃的那些软件上。就像很多成功的网站一样,eBay 已经看到了指数级的增长,指数级的增长对于架构不是友好的。支持 1996 年 eBay 的合适架构,对于 2006 年 eBay 来说,就不是合适的了。1996 年的架构无法处理 2006 年的负载,但是 2006 年的版本太过复杂而难以建立、维护,它是根据 1996 年的需求演化而来的。

        的确,这个原则可以引出工作的一种组织方式。在 Google,大家熟知的要求就是设计一个满足当前 10 倍需求的系统,这暗示着如果需求超过了一个数量级,那么扔掉并从头做起是更好的。每隔几年就被重新设计与抛弃的子系统而言,这是非常普遍的。

        的确,我们通常可以看到,刚接手一个成熟代码库的人们诟病架构的性能和可伸缩性。但是在软件系统的早期阶段,你不一定要确保它真正需要做什么, 关注灵活性以应付功能改变是十分重要的,而不是性能或可获得性。随着你的用户越来越多,你需要切换优先级了,不过让太多用户处在一个低性能的代码库,通常 是优先级较高的问题,而不是优先级低。Jeff Atwood 发明了一句话“性能是功能”,一些人把它理解成性能总是第一优先级。但是任何功能只是你不得不从其它功能选出的。这不是说,你应该忽视性能之类的工作—— 软件可以变慢、不可靠到断送一个业务——但是团队不得不与其它需求做出痛苦的抉择。通常有更多的业务决定,而不是技术上的决定。

        那么这意味着要故意选择一个可牺牲的架构吗?本质上它意味着接受,因为若干年后你将(如果顺利的话)扔掉当前创建的东东。这说明了要接受你堆放 在一起的、跨功能需求的限制。这也说明了现在需要考虑,当这一刻到来的时候,如何能够让其变得容易——软件设计师很少考虑如何设计他们的创造,以支持将来 的优雅替换。这也意味着在相对短的时间内被扔掉的软件,仍然带来了很大的价值。

        明白了你的架构是可牺牲的,不代表要放弃软件的内部质量。通常,被牺牲掉的内部质量,将比替代时间更早地伤害到你,除非你已经工作在行将退役的 代码库上。良好的模块化是健康代码库的关键部分,模块化通常在替换系统时起到巨大的帮助。对于系统的早期版本,要做的最好的一项工作就是搞清楚什么是最好 的模块化结构,为将来的替换做准备。虽说在早期牺牲一整个系统是合理的,但是如果具备良好的边界,随着系统的增长,牺牲单个模块是更有效率的。

        处理这个问题时,容易忽略的一个方面就是结算。是的,真的如此——我们已经陷入人们不情愿替代一个明显的、不可行的系统的状况,因为他们正在分期偿还代码库的方式。对于大企业,这更像是一个问题,但是不要忘了,只要你生活在这个世界上,就要去检查一下。

        你还可以把这个原则应用到现有系统的功能上。如果你正在开发新功能,那么只让一部分用户可见是明智的,因为你能够得到它是不是一个好想法的反 馈。为了达到这个目的,开始的时候,你可能在以一种可牺牲的方式在开发,你不必在一个功能上投入全力,因为你可能发现它不值得全面部署。

        模块可替换性对于微服务是一个主要论据,但是我谨慎推荐可牺牲的架构。微服务暗示着分布和同步,它俩都是复杂的助推器。我已经碰到过一些项目, 它们在走不是真正需要的微服务道路——结果严重减缓了它们的功能管道。因此,借助后来引入的微服务,逐渐将庞然大物分解,而庞然大物经常是好的可牺牲架 构。

        编写可牺牲架构的团队要决定牺牲它的时机。对于接手的新团队而言,他们讨厌现有代码、想重写,这是不同的情况。如果你没有理解已编写代码的上下文,你很容易就厌恶你没有编写的代码,明白你将要编写的代码属于可牺牲的代码,将是一个有用的变数。

        致谢

        与 Randy Shoup 的谈话启发了我,并帮助我构思了本文,尤其是 eBay(和一些 Google 的类似故事)的历史描述。 Jonny Leroy 指出了结算问题。Keif Morris, Jason Yip, Mahendra Kariya, Jessica Kerr, Rahul Jain, Andrew Kiellor, Fabio Pereira, Pramod Sadalage, Jen Smith, Charles Haynes, Scott Robinson and Paul Hammant 提供了有帮助的评论。