简言
在项目工作中,经常会有优化,有sql优化,项目架构优化,业务层优化,代码结构优化等,这些优化都是为了系统,易维护,易懂,易扩展。下面是我个人总结的一些经验分享与大家。我觉得每个程序需要成为架构师的必经之路。
以前觉得只需要发费更多时间在业务上,完成功能开发,自测通过,然后测试同学测试完,产品验收没问题就OK。慢慢的发现开始去追求更好,站在更高点去思考问题,慢慢开始成为老鸟了,如何提高代码质量,如何规范下面团队形成某种意识,自发去考虑代码的扩展性,建壮行等等。
1.代码优化是痛苦的
为何这样说呢?
往往代码优化是在以前的业务基础之上去优化别人或者自己代码基础上,提出优化想法,肯定是当前代码的可读性,维护性,设计方向不太友好,需要重新在现有业务上重构代码、或是架构。在优化前需要先去读懂现有代码逻辑,以及以前设计人,或业务发展,结合起来重构代码。
2.如何重构
-
小型重构
是对代码的细节进行重构,主要是针对类、函数、变量等代码级别的重构。比如常见的规范命名(针对词不达意的变量再命名),消除超大函数,消除重复代码等。一般这类重构修改的地方比较集中,相对简单,影响比较小、时间较短。所以难度相对要低一些,我们完全可以在日常的随版开发中进行。 -
大型重构
是对代码顶层进行重构,包括对系统结构、模块结构、代码结构、类关系的重构。一般采取的手段是进行服务分层、业务模块化、组件化、代码抽象复用等。这类重构可能需要进行原则再定义、模式再定义甚至业务再定义。涉及到的代码调整和修改多,所以影响比较大、耗时较长、带来的风险比较大(项目叫停风险、代码Bug风险、业务漏洞风险)。这就需要我们具备大型项目重构的经验,否则很容易犯错,最后得不偿失。
其实大多数人都是不喜欢重构工作的,就像没有人愿意给别人“擦屁股”一样,主要可能有以下几个方面的担忧:
- 不知道怎么重构,缺乏重构的经验和方法论,容易在重构中犯错。
- 很难看到短期收益,如果这些利益是长远的,何必现在就付出这些努力呢?长远看来,说不定当项目收获这些利益时,你已经不负责这块工作了。
- 重构会破坏现有程序,带来意想不到的Bug,不想去承受这些意料之外的Bug。
- 重构需要你付出额外的工作,何况可能需要重构的代码并不是你编写的。
3.为何需要重构
程序有两面价值:“今天可以为你做什么” 和 “明天可以为你做什么”。大多数时候,我们都只关注自己今天想要程序做什么。不论是修复错误或是添加特性,都是为了让程序力更强,让它在今天更有价值。但是我为什么还是提倡大家要在合适的时机做代码重构,原因主要有以下几点:
- 让软件架构始终保持良好的设计。改进我们的软件设计,让软件架构向有利的方向发展,能够始终对外提供稳定的服务、从容的面对各种突发的问题。
- 增加可维护性,降低维护成本,对团队和个人都是正向的良性循环,让软件更容易理解。无论是后人阅读前人写的代码,还是事后回顾自己的代码,都能够快速了解整个逻辑,明确业务,轻松的对系统进行维护。
- 提高研发速度、缩短人力成本。大家可能深有体会,一个系统在上线初期,向系统中增加功能时,完成速度非常快,但是如果不注重代码质量,后期向系统中添加一个很小的功能可能就需要花上一周或更长的时间。而代码重构是一种有效的保证代码质量的手段,良好的设计是维护软件开发速度的根本。重构可以帮助你更快速的开发软件,因为它阻止系统腐烂变质,甚至还可以提高设计质量。
3. 如何重构
小型重构
小型重构大部分都是在日常开发中进行的,一般的参考标准即是我们的开发规范和准则,目的是为了解决代码中的坏味道,我们来看一下常见的坏味道都有哪些?
if-else过多
比例代码:
if(){
if(){
if(){
} else if(){
}
} else if(){
}
} else if(){
}
if else建议不超过三层
for循环层次多,同上 if else 列子
重复代码
项目中有多个地方有计算除余结果值,我们可以考虑通过函数封装起来,统一调用方法,或者相似代码片段可以统一抽取方法调用。
函数过长
一个好的函数必须满足单一职责原则,短小精悍,只做一件事。过长的函数体和身兼数职的方法都不利于阅读,也不利于进行代码复用。
命名规范
一个好的命名需要能做到“名副其实、见名知意”,直接了当,不存在歧义。
不合理的注释
注释是一把双刃剑,好的注释能够给我们好的指导,不好的注释只会将人误导。针对注释,我们需要做到在整合代码时,也把注释一并进行修改,否则就会出现注释和逻辑不一致。另外,如果代码已清晰的表达了自己的意图,那么注释反而是多余的。
无用代码
无用代码有两种方式,一种是没有使用场景,如果这类代码不是工具方法或工具类,而是一些无用的业务代码,那么就需要及时的删除清理。另外一种是用注释符包裹的代码块,这些代码在被打上注释符号的时候就应该被删除。
过大的类
一个类做太多事情,维护了太多功能,可读性变差,性能也会下降。举个例子,订单相关的功能你放到一个类A里面,商品库存相关的也放在类A里面,积分相关的还放在类A里面……试想一下,乱七八糟的代码块都往一个类里面塞,还谈啥可读性。应该按单一职责,使用不同的类把代码划分开。
这些都是比较常见的代码“坏味道”,实际开发中当然还会存在其他的一些“坏味道”,比如代码混乱,逻辑不清晰,类关系错综复杂,当闻到这些不同的“坏味道”时,都应该尝试去解决掉,而不是放纵不管不顾。
大型重构
相对小型重构,大型重构需要考虑的事情比较多,需要定好节奏,按部就班的执行,因为在大型重构中,情况多变。
将大象装进冰箱的步骤一般可以分成三步:1)把冰箱门打开(事前);2)把大象推进去(事中);3)把冰箱门关上(事后)。日常所有的事情都可以采用三步法进行解决,重构也不例外。
事前
事前准备作为重构的第一步,这一部分涉及到的事情比较杂,也是最重要的,如果之前准备不充分,很有可能导致在事中执行或重构上线后产生的结果和预期不一致的现象。
在这个阶段大致可分为三步:
-
明确重构的内容、目的以及方向、目标
在这一步里面,最重要的是把方向明确清楚,而且这个方向是经得起大家的质疑,能够至少满足未来三到五年的方向。另外一个就是这次重构的目标,由于技术限制、历史包袱等原因,这个目标可能不是最终的目标,那么需要明确最终目标是怎么样的,从这次重构的这个目标到最终的目标还有哪些事情要做,最好都能够明确下来。 -
整理数据
这一步需要对涉及重构部分的现有业务、架构进行梳理,明确重构的内容在系统的哪个服务层级、属于哪个业务模块,依赖方和被依赖方有哪些,有哪些业务场景,每个场景的数据输入输出是怎样的。这个阶段就会有产出物了,一般会沉淀项目部署、业务架构、技术架构、服务上下游依赖、强弱依赖、项目内部服务分层模型、内容功能依赖模型、输入输出数据流等相关的设计图和文档。 -
项目立项
项目立项一般是通过会议进行,对所有参与重构的部门或小组进行重构工作的宣讲,周知大概的时间计划表(粗略的大致时间),明确各组主要负责的人。另外还需要周知重构涉及到哪些业务和场景、大概的重构方式、业务影响可能有哪些,难点及可能在哪些步骤出现瓶颈。
事中
事中执行这一步骤的事情和任务相对来说比较繁重一些,时间付出相对比较多。
- 架构设计与评审
架构设计评审主要是对标准的业务架构、技术架构、数据架构进行设计与评审。通过评审去发现架构和业务上的问题,这个评审一般是团队内评审,如果在一次评审后,发现架构设计并不能被确定,那就需要再调整,直到团队内对方案架构设计都达成一致,才可以进行下一步,评审结果也需要在评审通过后进行邮件周知参与人。
该阶段产出物:重构后的服务部署、系统架构、业务架构、标准数据流、服务分层模式、功能模块UML图等。
- 详细落地设计方案与评审
这个落地的设计方案是事中执行最重要的一个方案,关系到后面的研发编码、自测与联调、依赖方对接、QA测试、线下发布与实施预案、线上发布与实施预案、具体工作量、难度、工作瓶颈等。这个详细落地方案需要深入到整个研发、线下测试、上线过程、灰度场景细节处包括AB灰度程序、AB验证程序。
在方案设计中最重要的一环是AB验证程序和AB验证开关,这是评估和检验我们是否重构完成的标准依据。一般的AB验证程序大致如下:
在数据入口处,使用相同的数据,分别向新老流程都发起处理请求。处理结束之后,将处理结果分别打印到日志中。最后通过离线程序比较新老流程处理的结果是否一致。遵循的原则就是在相同入参的情况下,响应的结果也应该一致。
在AB程序中,会涉及到两个开关。灰度开关(只有它开启了,请求才会被发送到新的流程中进行代码执行)。执行开关(如果新流程中涉及到写操作,这里需要用开关控制在新流程写还是在老流程中写)。转发之前需要将灰度开关和执行开关(一般配置到配置中心,能随时调整)写入到线程上下文中,以免出现在修改配置中心开关时,多处获取开关结果不一致。
- 代码的编写、测试、线下实施
这一步就是按照详细设计的方案,进行编码、单测、联调、功能测试、业务测试、QA测试。通过后,在线下模拟上线流程和线上开关实施过程,校验AB程序,检查是否符合预期,新流程代码覆盖度是否达到上线要求。如果线下数据样本比较少,不能覆盖全部场景,需要通过构造流量覆盖所有的场景,保证所有的场景都能符合预期。当线下覆盖度达到预期,并且AB验证程序没有校验出任何异常时,才能执行上线操作。
事后
这个阶段需要在线上按照线下模拟的实施流程进行线上实施,分为上线、放量、修复、下线老逻辑、复盘这样几个阶段。其中最重要最耗费精力的就是放量流程了。
-
灰度开关流程
逐步放量到新的流程中进行观察,可以按照1%、5%、10%、20%、40%、80%、100%的进度进行放量,让新流程逐步的进行代码逻辑覆盖,注意这个阶段不会打开真实执行写操作的开关。当新流程逻辑覆盖度达到要求、并且AB验证的结果都符合预期后,才可以逐步打开执行写操作开关,进行真实业务的执行操作。 -
业务执行开关流程
在灰度新流程的过程中符合预期后,可以逐步打开业务执行写操作开关流程,仍然可以按照一定的比例进行逐步放量,打开写操作后,只有新逻辑执行写操作,老逻辑将关闭写操作。这个阶段需要观察线上错误、指标异常、用户反馈等问题,确保新流程没有任何问题。
放量工作结束后,在稳定一定版本后,就可以将老逻辑和AB验证程序进行下线,重构工作结束。如果有条件可以开一个重构复盘会,检查每个参与方是否都达到了重构要求的标准,复盘重构期间遇到的问题、以及解决方案是什么样的,沉淀方法论避免后续的工作出现类似的问题。
总结
代码技巧
- 写代码的时候遵循一些基本原则,比如单一原则、依赖接口/抽象而不是依赖具体实现。
- 严格遵循编码规范、特殊注释使用 TODO、FIXME、XXX 进行注释。
- 单元测试、功能测试、接口测试、集成测试是写代码必不可少的工- 具。
- 我们是代码的作者,后人是代码的读者。写代码要时刻审视,做前人栽树后人乘凉、不做前人挖坑后人陪葬的事情。
- 不做破窗效应的第一人,不要觉得现在代码已经很烂了,没有必要再改,直接继续堆代码。如果是这样,总有一天自己会被别人的代码恶心到,“出来混迟早是要还的”。
重构技巧
- 从上至下,由外到内进行建模分析,理清各种关系,是重构的重中之重。
- 提炼类,复用函数,下沉核心能力,让模块职责清晰明了。
- 依赖接口优于依赖抽象,依赖抽象优于依赖实现,类关系能用组合就不要继承。
- 类、接口、抽象接口设计时考虑范围限定符,哪些可以重写、哪些不能重写,泛型限定是否准确。
- 大型重构做好各种设计和计划,线下模拟好各种场景,上线一定需要AB验证程序,能够随时进行新老切换。