聊一聊:一个大型软件系统该如何重构

  在互联网行业,每当新员工入职一家新公司时,都要学习一套新的软件系统。如果该公司的代码非常规范,架构设计非常合理,那么新员工上手的速度会非常快。当然,你这个螺丝钉的角色也就非常明显了。另一方面,如果面对『屎山』一样的祖传代码,就会有很多抱怨,学习起来也很痛苦。

  从质量上,我把软件大致分为以下几种类型:

  第一种:它们对稳定性、规范性要求非常高,所以代码中异常判断、校验非常多,代码看上去就很冗余,最典型代表是华为的电信级产品。第二种:写得就很随意,风格各式各样,代表的例子是开源项目。第三种:也是最糟糕的,员工在 PM(产品经理)的高压下日夜加班赶出来的,毫无设计风格,能跑就行,追求最快速度地实现。

  重构应该也是互联网公司开发工作的一部分吧。当一个软件系统需要重构时,必然是因为以下某种原因:比如二次开发难度越来越大、新员工上手越来越困难,每个模块只有特定的人懂;面对日益增长的功能需求,现有架构已经满足不了;模块耦合非常严重,导致不同的开发团队之间互相依赖,严重阻碍了开发进度。

  模块之间耦合严重,类间调用关系形成双向依赖。大量的 if/else 分支、运行流程分支太多,圈复杂度爆表,根本理不清。当我们想要增加一个新功能,即使这个功能很小很小,牵扯的链条也非常长,涉及需要改动的周边函数、文件数量巨多。

  软件系统从最初的 demo 开始,不断完善,一点点添加新功能,以适应不同的应用场景。比如滴滴这款软件,最初只要把司机端和乘客接进来就行,加上调度系统负责派单,用户规模也比较小;后来,用户规模陡增,需要扩展服务器数量,又牵扯到负载均衡、消息中间件、高并发等需求 (分布式技术 3:高并发系统的设计思路以及C++ 实现 - 知乎);再后来,添加各种服务类型,比如顺风车、专车、豪华车等等,然后又是各种红包、优惠券等。

  微服务的前提条件就是模块间能解耦,这不仅是上云的需求,也能提高研发团队整体的开发效率,更重要的是为了实现服务编排,可以给任意子的服务提供灵活的资源,从而最大化集群的资源利用率,也就是说能更好地做到弹性扩缩容和容错。(从单体到微服务,论软件系统如何逐步地进行解耦 - 知乎)

  传统概念中对代码重构的理解是『不引入任何新功能』。我的看法是,代码重构和新功能开发结合起来,这样更有利于最大化重构效果。

  重构也是软件架构设计的一种,这里我称之为『重构设计』。

  首先,你要清楚重构的目标是什么。比如侧重满足二次开发,或者侧重模块解耦,或者兼容各种硬件平台、编程语言等等。

  其次,你要对基本的软件架构和软件设计风格有清晰的了解,以下是一些必备技能:

  模块原则:使用简洁的接口拼合简单的部件清晰原则:清晰胜于技巧组合原则:设计时考虑拼接组合分离原则:策略同机制分离,接口同引擎分离简洁原则:设计要简洁,复杂度能低则低吝啬原则:除非却无他法,不要编写庞大的程序透明性原则:设计要可见,以便审查和调试健壮原则:健壮源于透明与简洁表示原则:把知识叠入数据以求逻辑质朴而健壮通俗原则:接口设计避免标新立异缄默原则:如果一个程序没什么好说的,就沉默补救原则:出现异常时,马上退出并给出足够多的错误信息经济原则:宁花机器一分,不花程序员一秒生成原则:避免手工hack,尽量编写程序去生成程序优化原则:雕琢前要先有圆形,跑之前先学会走多样性原则:绝不相信所谓”不二法门“的断言扩展原则:设计着眼未来,未来总比预想来得快

  23 种设计模式,你不一定要完全了解代码怎么写,但一定要知道每一种设计模式背后的设计思想是什么。有一段时间,我试图在我的代码应用各种设计模式,可最终代码看起来特别冗余而且不是那么必要。从个人经验上来讲,平时业务代码中用设计模式的场合非常少,最常用的无非是工厂、适配器、责任链等,而且效果并没那么大,设计模式真正适合的场合是更高层级的,比如模块间设计等等。

  单一职责、开闭原则、里氏替换、迪米特法则、接口隔离、依赖倒置。

  有了解过一些,没有亲身实践过。它大体上就是模块解耦和分层的思想:API(对外访问层)、Domain(领域层)、Repository(数据源访问代理层)及基础设施层(DB、Redis、HTTP、RPC 等)

  参考马丁.福勒那本经典的《重构:改善代码的设计》,比如过长的函数、过长的参数、数据泥团怎么处理等等。

  这里,也说下个人的小建议:比如纵向的调用关系变为横向的,减少函数调用栈深度;不要过度封装。相信用户能找到底层类的实现接口;逻辑上相关的代码物理上尽可能放在一块;对于某个小的具体功能,涉及的链条越短越好;面向接口编程;访问 A 表数据的 class 中不能存在访问 B 表数据的 function;模块对外暴露的接口部分,数据类型的选择上尽量做到宽进严出(接口要考虑通用性);写操作接口,接收参数尽可能少;读操作接口,返回参数尽可能多;减少不必要的类和数据结构等等。

  详解微服务之间3大通信方式:网关 API、RPC 和 SideCar - 知乎

  毫无疑问,云是未来数字世界的基础设施。

  详解云原生五大关键技术 - 知乎

  比如约定/注入插件、事件插件、插槽插件等等。

  插件的一个简单实现 - 知乎

  好了,这里不再列举了,因为根据哈弗大学心理学博士米勒的研究,对于每一类产品,用户最多只能记住七类品牌,这里我也就列七个重构原则~

  以面向对象语言为例,这里我把它分为了几个步骤:

  旧系统的类间调用关系图 vs 新系统的类间调用关系图旧系统的整体架构图 vs 新系统的整体架构图旧系统的运行流程图 vs 新系统的运行流程图分模块逐一拆解:先画出重构前的样子,再画出重构后的样子新增功能有哪些,怎么在重构后的系统里添加考虑对旧系统的影响:兼容性问题考虑实施可行性: 重构成本和收益之间如何权衡

  总体上有两种方式:

  1. 新建分支(不推荐)

  2. 新建目录(推荐),这样能保证原来旧的那套代码能用。这里又有两种方式:

  做减法(先跑通旧的代码,然后一点点删除冗余代码,或重构具体子模块)-- 推荐,因为如果实在没有开发时间了,某些未完成的模块还可以沿用以前的老代码。做加法(从每个子功能开始重构,相当于重新构建这套软件系统)-- 不推荐,因为时间成本不可控

  用设计原则和架构理论武装自己,阅读优秀的源码验证理论,深入理解具体业务,方能设计出一套优雅的软件系统。

  各位朋友们,想要了解更多方面的技术内容,请关注本人同名zhihu 账号“自由技艺”,上面的内容更新的会比较勤哦~

posted @ 2022-02-13 16:55  ebuybay  阅读(296)  评论(0编辑  收藏  举报