/ 设计

聊一聊复用

复用是每一个工程师的追求,无论是设计还是编码我们最常说的除了“产品是SB”、“进度赶不上”外,恐怕就是“要好好复用”了吧。当一个应用或者一个系统日趋复杂的时候,我们总是希望其维护性不会线性地降低,而解决这一问题的方法上,就往往归结为简单的“复用”二字。

在与不少同学的沟通中,我发现有一部分工程师对“复用”的理解是有失偏颇的,在此针对比较容易出现的几类曲解进行一下解说。

复用的目的

我的论断很简单:

复用有很多的目的,但少写代码不应是其中一个。

注意这边说的是,少“写”代码不应该是复用的目的之一。所谓的“写”,是一个人所做出的行为,复用作为一种工程手段,其应该解决的是工程的问题,而不是人的问题。

更直白地说,要少“写”代码,最好的手段是复制+粘贴大法,而不是费尽心思去调整代结构、做逻辑拆分、应用各种模式。

但事实上,在支持追求复用的过程中,我们确实往往可以做到少写代码的效果。因此少写代码其实是复用的一种“副作用”,而不是目的。

所以,与人讨论代码的设计与实现时,千万不要说出“可以少写点代码”这样的理由,我会欢迎你去使用复制粘贴。

复用的对象

我的结论:

复用的对象是逻辑,而非代码。

在现实中,代码是对逻辑的一种面向计算机的表达,即先有逻辑,其后才有代码。因此面向逻辑的复用会映射为代码的复用,但反之却并不一定成立。

更重要的是,如果只着眼于对代码的复用,容易产生错误的结果。其原因在于,代码是一时的静态表现,即是一个瞬间的快照,其本身没有过去和未来的属性;而逻辑则是不断发展的,逻辑有其曾经的状态,现在的状态,未来可能的状态。因此当基于逻辑去思考的时候,我们会发现一些有趣的事:

这叫完整而一致的复用。

这叫基于精致的设计的复用。

这叫正常人都不会去复用。

这叫虽然看上去能复用但你真做了就完蛋了。

从上面各种状态可以看到,对一个场景是否复用的判断,来自于其当前的状态未来的状态想结合的考虑,而不是单纯的当前静止状态。而从代码层面着手我们没有未来的信息,容易出现第4种情况。也就是说:

复用所要看的不仅仅是当下,更是未来的发展趋势是否一致。

同时:

参与业务的复用,在不理解业务的前提下往往会起到反作用。

为了更好地说明这一现象,举一个实例:

小明和他的消息系统

小明负责的系统中,出现了一个需求:要求系统右上角显示未读的系统公告的数量,公告通过每5秒一次的请求拉取。

小明作为一个优秀的工程师,这样的场景自然不在话下,很快地完成了应有的结构:

每5秒从服务器获取数据并渲染,这是很直观的逻辑。

随后,系统有了新的需求:要求系统右上角增加显示好友发送的信息的数量,同样每5秒一次拉取。

有了新的需求,再回头看一眼已经有的系统公告的数量,小明果断地做出了这样的结构:

设计中充分使用了复用的思想,将“每5秒一次拉取”的代码进行了复用,通过注册URL等信息,由不同模块完成数据的自理和渲染,形成一个中心+可扩展的处理器的典型结构,可谓中规中矩。

需求是永无止境的,系统又有了新的需求:由于用户的需要,好友的信息需要实时显示,因此要求将5秒一次的拉取改为服务器端的推送。

面对这样的需求,小明回头看了看已经存在的“良好复用”的结构,他是这么说的:

靠,整个设计都要推翻重来了!

所以这是成功的复用设计吗?显示不是。这一设计并没有提升系统的维护性,面对新的需求无能为力。

但是问题存在于哪呢?

是设计的结构不合理吗?不尽然,如果新的需求是“又增加一种消息的提示”,那么原本的结构是非常合理可用的。

是需求太变态吗?自然不是,这一需求无论如何合情合理。

这里问题的根本在于,小明关注的是代码上要如何复用,要怎么样可以让重复的代码减少,而没有从逻辑上去看待这一需求场景。事实上,“系统公告”和“用户消息”原本就是两类东西,后者的实时性是刚需,而前者更重于“通知到达”这一事实,并不在乎其实时性。面对原本针对性指标就不同的两个需求,在不同之处却采用了复用的方案,显然是留下了隐患。

如果从一开始就分析逻辑,从逻辑的角度着手,那么很可能得到的结构就是:

将变化的点“数据的推送”抽象出来,使用组合的方式提供实现。

这样当从5秒一次的拉取变为实时的推送时,我们结构的变化将是:

很好地使用扩展来满足新的需求,而不用将整个设计推倒重来。

这就是为什么我们要从逻辑的层面上去审视、设计复用的结构,因为从逻辑上我们可以分析出未来的变化,从而用更正确的模型去应对。这也是为什么复用往往并不是一个纯开发上的任务,而需要更多对业务的洞察。