你好,我是郑晔!

前面两讲,我们分别讲了如何从头开始设计一个程序库和应用。但是在实际工作中,有很多时候,我们的工作并不是从头设计一个应用,而是改进一个既有项目的代码。既有项目的代码意味着什么呢?意味着各种问题。

我们一直在说,软件设计是一门关注长期变化的学问。越是在商业上成功的软件,存续的时间往往越长。存续的时间越长,往往就会有更多的麻烦。

我们先不说有些项目一开始就没有设计,一路混乱向前。即便是一个最初有着还算不错设计的项目,随着时间的积累、人员的更替、把前人的做法当作惯例等等事情的发生,项目的设计就会逐渐变得不堪重负。

我在前面的课程中也举过一些例子,虽然每人只改了一点点,最后却是积重难返。这就是一个项目缺乏设计守护的结果。好的守护可以使设计更持久,遗憾的是,大多数项目做得并不好。

除了上面这几点,还有一点就是,新的技术和框架会不断涌现,旧代码往往是不能有效使用这些新东西的。比如,Java 世界今天开发的主流是 Spring Boot,然而十年前,它还不存在。

虽然那时候已经有了 Spring,但那时候的主流开发方式还是打出一个 WAR 包,再部署到 Tomcat 上。所以,新出现的很多技术会提供更简单的做法,替换掉旧代码中笨拙的部分。

所以,到底怎么才能让自己的项目在设计上不断地演进,跟上时代发展的步伐,不断焕发新的活力呢?对于任何一个开发团队而言,这都是一个值得考虑的问题。

那么,这一讲,我们就来谈谈如何改进既有项目的设计。

从目标开始

在我的另外一个专栏《10x 程序员工作法》中,我讲过一个类似的主题,如何面对遗留系统。那里面的主要观点就是我们应该找到一个目标,然后小步改进,逐步向这个目标接近。

在那一讲中,我讲的重点主要在于改进的过程,而在这里,我打算从设计的角度再来审视一下这个问题。既然都是我的专栏,所以二者在解决问题上的思路一致的,都要先从找到目标开始。

大多数团队一说起改进,一般想的都是功能性方面的目标。比如,我原来的系统能支持 100 万的用户,现在要支持 1000 万的用户。这种改进固然是我们需要考虑的,甚至是迫不得已的。

但这种改进解决的是实现,因为不同量级的系统根本就不是一个系统,承载的用户量发生了变化,其实是一种需求的变化。但是,这种改变并不会让你的设计变好。

既然我们已经决定要改进了,就应该好好地把设计改进一下,而不只是把功能重新实现一遍。因为功能实现是你无论如何都必须做的,都是为别人做的,而设计的改进才是你为了自己做的,因为在未来的一段日子里维护这些代码的人是你。如果我们要做设计的改进,设定好改进设计的目标就显得尤为重要。

那设计改进的目标应该是什么呢?你可以先问一下自己这样一个问题,如果有机会从头设计这个系统,它应该是什么样子呢?

这个问题可能会让很多程序员一下子愣住,因为他们每天都陷于忙碌的工作中,做的工作都是各种微调、各种打补丁,眼中只有一个具体微观的世界,却不曾有一个整体的思考。

是的,从头来过,它应该是什么样子。这是一个简单的问题,也是一个困难的问题。简单在于,它的字面意思很好理解。困难却在于,很多人一听到这个问题,直觉就要回避:

  1. 我的系统已经这么沉重了,怎么可能重来?
  2. 我有那么多的需求要做,哪有时间重做一遍?
  3. 我的系统那么复杂,重做一遍,出了问题谁来负责?

我承认,这些都是很现实的问题。但是,我的意思并不是让你真的一上来就动手,从零开始把系统重写一遍。这里的重点在于,我们要找到改进的目标,也就是一个系统本来应有的面貌

这就是为什么我们前面要学习那么多设计一个系统的知识,否则,我们没有一个设计知识的沉淀,所谓的“重新设计”,弄不好我们就会回到原来的老路上去。

这时候,或许你突然想到一个严重的问题了,开启一次系统改进,如何处理人们的共识好像也是一件困难的事情,但这根本不是一个设计问题。想要真正地开启一次改进,就要让人们意识到,设计一个系统和实施一次系统改进是两个完全不同的问题,可以分阶段地进行

我们只有把系统设计成它应有的样子,才算是确定了我们的目标。有了目标之后,接下来,我们才能制定改进路径,而把现有的系统一点一点从旧有的样子改动成新的样子,这是实施的过程。

好!我假设你已经搞定了周边人的共识,准备着手进行改进了。

改进的过程

现在你要重新设计这个系统了,或许你会想,这有什么难的?不就是照着原来的需求,重新来一遍吗?如果你真的还有原来的需求,能让你照着设计一遍。我真的只能说,你太幸运了。

在大部分真实的项目中,一个既有系统的情况是,没有人能够说出它到底承载了哪些需求。当然,主干部分是人人都知道的,但主干常常是九牛一毛,而更多的细节隐藏在代码中了。

一个长期存在的系统,开发者可能已经换了好几拨。了解当年那些需求的人可能早已不知所踪了,导致的结果就是,每一个工作在这个项目上的人都是只见树木不见森林。

在这种情况下,我们该怎么办呢?我给你一个入手的起点,就是接口

在第 4 讲学习怎样理解一个系统的设计时,我们曾经说过,想要理解一个系统的设计,可以按照模型、接口和实现的这个框架去理解,其中,接口是模型能力的体现。

对于一个系统而言,接口也是使系统内部状态发生改变的原因,系统中的所有变化必然都是从某个接口开始的。既然没有人能够清楚地说明系统的现状,那么,我们从接口入手,了解系统的现状是一个非常现实的做法。毕竟,接口是不会骗人的。

不过,这里的接口不仅包括我们传统意义上的接口,也包括各种后台服务。前面我们讲了很多构建模型的内容,有了这个基础,我们再看后台服务,就会发现,后台服务只不过是按照某种规则触发模型的接口。比如,定时服务,就是定时地去调用模型的接口。所以,我们也要把这种接口梳理出来。

有了对于这些接口的了解,我们就对这个系统呈现哪些能力有一个认识了,就相当于获得了一份需求描述。基于这个认识,我们来构建我们新的设计。

接下来,我们就要重新设计了,这个改进设计的难点就是不要回到老路上。我们需要按照一个正常设计的思路去走,该分离关注点的分离关注点,该重新组合的要重新组合。

之所以我要提示这一点,就是因为思维的惯性实在是太大了。比如说,在原有的系统内有一个叫订单的概念,我们就会习惯性地使用订单,而不是把商品订单、支付订单等概念分开。

一般而言,既有项目的设计有一个很大的问题就是各种信息混在一起,而能够把不同的信息拆分开来,对于设计而言,就是一个巨大的进步。

做好了新的设计,也就为我们后续的行动找到了新的方向。接下来,我们要做的是,对比新旧设计,找到一条改进路径。

**永远不要指望一个真实的项目停下来,一步到位地进行改进。**我们能够做的,唯有小心翼翼,一步一步向着目标前进。

对于不同的项目,选择的路径可能是不同的,有人会选择关键路径上的关键模块进行改进,也有人会选择影响较小的模块先进行探索,无论是哪种方案都是可以的。一个关键点就在于,动作要小。

学习过我的两个专栏的同学可能已经充分理解了我对小步前行的喜爱了。任何一个大动作,往往都意味着很长时间无法完成。在这个过程中,所有人都会提心吊胆。如果不能看到成果,很多人的信心都会随时间流失。所以,在软件设计的改进过程中,积小胜为大胜才是一个合理的选项

还有一个关键点,要让所有相关利益人有一个共识。我又一次说到了共识,软件开发虽然是一个技术活,但归根结底还是一项团队活动,是一项人的活动。

既然涉及到诸多参与者,就一定要让大家形成一个共识。所以,系统改进,尤其是一个规模比较大的系统改进,一定要让所有人有共识。无论是开会也好,宣讲也罢,让大家对于改进的原因和改进的计划有个共同的预期是至关重要的。

更加具体的改进过程,我在《10x 程序员工作法》中有更细节的讨论,有兴趣的话,可以去参考一下。

虽然我在这里讲的是一个系统的改进过程,其实,同样的思路也可以运用在更小的模块中。只不过,更小模块意味着更少的接口、更低的复杂度以及更少的相关利益人。事实上,我反而鼓励你从小模块入手,一步到位去改进整个系统,难度系数是更大的,而小模块可以帮助你积累更多改进的经验,无论是设计,还是与人打交道。

总结时刻

今天,我给你讲了如何改进一个既有软件的设计。一个软件放在时间长河中会有很多东西发生改变,即便是当初还算不错的设计,随着时间的累积,也可能积重难返。

改进一个软件的设计,首先,要确定改进的目标。改进的目标就是,重新设计这个软件,它应该设计成什么样子,让设计还原到它应有的本来面貌。寻找改进的起点,一部分可以从需求入手,还有一部分要从梳理接口入手。

设计改进的难点在于不要回到老路上,要做正常的设计,尤其是要把分解做好。

有了改进目标之后,接下来就是要找到一条改进路径,选择怎样的路径都是有道理的,但有两个关键点是非常重要的,一个是每步改进的动作要小;一个是要让相关利益人达成共识。

如果今天的内容你只能记住一件事,那请记住:改进既有设计,从做一个正常的设计开始,小步向前

思考题

最后,我想请你回想一下,你的系统在设计上存在着哪些问题,你打算怎么改进它呢?欢迎在留言区分享你的想法。

感谢阅读,如果你觉得这一讲的内容对你有帮助的话,也欢迎把它分享给你的朋友。