漫谈之重构手段

miloyang
0 评论
/ /
948 阅读
/
6292 字
09 2024-02

重构是经济适用性,并非道德使然

上一章节为重构的原则,嗅出代码中的坏味道实现 。接下来继续分享。

什么时机开启重构?

1: Code review:在别人code review的时候嗅出代码/架构上面的坏味道,在非暴力沟通,给出具体修改意见的前提下建议。一定记得,codereview不是挑战,是为了团队一起成长。
2: 每次commit代码的时候:每一次经你之手提交的代码都应该比之前更干净。使用IDE的commit的时候,再一次查看代码的实现是否有坏味道。
3:当你接手一个异常难读的项目时:说服项目组将重构作为一项需求任务来做,代码是给人看的,如果难读,第一是写的不够优雅,第二是你自身理解能力或知识储备不够,但是正常的程序员都可以分辨出来,如果是写的不够优雅,那么重构吧,因为你接手了,后续就是你维护,为了下一次更快速的迭代,所以不要犹豫提出来重构。但是一定要说服你的项目成员,因为这个项目可能是涉及到很多的服务修改。这是一个挑战,如果挑战失败,那么增量式的重构吧。
4:当迭代效率低于预期时:将重构当做一个项任务专门来做,必要的时候停下来迭代需求。这也是一个非常好的借口,比如团队一周的工时,远远低于隔壁团队的需求量,这时候就可以提出重构,大家肯定不会说是自己的能力问题,都会归咎于历史,则是一个很好的契机。

两类程序员,一是懒惰的,肯定不会重构,二是勤奋的,看到代码就想重构,但毕竟都是业务驱动,建议遵循:第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是可以去做;第三次再做类似的事,你就应该重构。正如老话说的:事不过三,三则重构。

重构的手段

重构这本书下半部分都是将重构的手段,全部整理出来与书无异,本文借助其他博主归类(个人觉得归类的很准确),外加自己的理解加以整理。 下面是自底向上的模式。

名称 目的 场景 做法
提炼变量 让表达式更加可读 当存在难以阅读表达式时,比如if里面写了各种判断 1:划分子表达式<br>2:使用有意义的名称命名子表达式
提炼函数 将意图与实现分离 1:大量重复的逻辑;2:需要浏览一段代码才能理解这个函数是干什么的 1:识别变量依赖;2:给函数命名;3:确定函数存放位置
封装类型 高内聚,低耦合 1:大量相似变量被同时传递;2:存在大量参数与逻辑无序关心 1:发现相关性;2:抽象概念;3:具象化一种类型;4:设计合理名称以及生命周期
模块划分 逻辑划分,代码复用 当一些类需要频繁配合完成一个独立的功能时,人以类聚物以群分。 1:关注点分离,抽象出模块;2:模块内部紧密协作;3:外部接口调用;4:模块之间保持单一进行依赖
封装阶段(流水线) 保持单一原则 1:一段函数处理存在多种事情时;2:多个循环泥团一起时;3:逻辑分支冗余 1:抽象出多个任务阶段;2:阶段和参数对象进行分离;3:利用接口/多态特性替换if/else模式
委托模式 分离变化和不变的逻辑 1:被调用方会频繁变更时 封装接口,通过依赖倒置隔离下游变更
服务化/多进程 资源隔离(机器&人力) 1:某一接口需要大量的状态和资源时;2:提高运行时的稳定性时候. 1:注册新的git仓库;2:迁移代码,搭建CICD流程;3:人力隔离,一个人力负责一个服务.
配置化 提高研发效率,减少重复需求 1:存在反复的大量的重复需求时;2:业务需求频繁,疲于应付需求,如运营需求相关 抽象通用的业务模型,剥离出来.
领域化 微服务的划分,拆分核心领域 当具有一组完整业务交付价值的功能需要复用时 1:提供完整的架构方案;2:设计对外接口提供SDK;3:完善接入流程;4:搭建开放平台
中台化 研发效能复用 当公司多个业务具有相同功能时 1:在系统化的基础上进行多租户化;2:权限/资源管理
平台化 自动化业务流程 1:当业务需求模式清洗;2:研发成本成为最大成本时;3:规模化获客接入时 1:抽象自动化流程;2:研发介入流程配置化;3:配置通过平台调度自动化;4:权限/资源/计量/计费/数分
产品/开源化 公司外部提供产品服务,产品复杂度过高 1:技术资源不能创建更大价值时;2:市场需求明确,规模化获客与续约不成问题时;3:用户增长逻辑明确 1:提供对外open api;2:提供产品化的角色/权限/资源管理能力;3:恰到好处的处理客户使用问题

重构的基本步骤

1.代码分析

通读代码,分析现状,找到代码在各个层面的坏味道,嗯,代码很难读我知道,但是没办法,硬啃吧。

2.重构计划

重构应该永远是一种经济驱动的决定。

  • 列举出所有坏味道的实现,并向团队给出重构的理由以及重构的计划。
  • 确定重构后的目标,明确的描述出重构后达到的预期是什么。
  • 重构计划中,必须要有测试验证方案,保证重构前和重构后软件的行为是一致的。测试验证方案相当于打仗的粮草,是需要先行的.
  • 如果没有这样的方案,那就必须让软件具有可测试性
  • 宣讲后如果无法得到团队的认可,那得先分析具体原因,是已经习惯了这种坏味道的研发模式,写起来爽,还是觉得重构是没有价值的。如果是前者,那就自己列举出重构计划,增量迭代,在commit中写清楚,并与codereview的同事讲解清楚修改点以及范围。如果是后者,那就把重构这本书推荐给团队,先给出一个MVP(最小可行性模型),这个MVP甚至是在宣讲的时候就应该给出才具有说服力。因为重构始终是对自己有利的。
  • 将重构任务当做项目来管理,需要在每天的工作清单中进行,需要登记具体的工时,对指定任务的人明确排期和进度同步,每周需要周会总结本周的重构进度。
  • 保证现有的功能是完整的,不会阻碍下一任务的进行。

小步子策略

大家都抗拒重构,都认为代码可以运行,就不要动的理念,都抱着多一事不如少一事的想法,因为重构的效果不是那么的立竿见影。根据破窗定律,大家的代码会越来越烂,越来越难以维护,最终会导致整艘船沉没。当然,如果一上来就说整个项目重构,估计大家都会觉得你疯了,我们可以:

  • 将重构任务拆分成 每周都能见到一点效果的小任务,这样慢慢的卷起来吧。
  • 每一步重构都要具有收益,并且可测试,不能阻断当前需求的迭代。
  • 重构任务必须要被跟踪,要定期的开会同步进度,来不断加强团队的重构意识。不能埋头苦干,让调动大家一起来做。

测试驱动

  • 对于小型软件,需要先补充单元测试再进行重构
  • 对于大型软件,先搭建自动化测试流程,再进行重构。
  • 对于复杂的不确定性业务,使用 ab test来验证重构对指标的影响,避免造成损失。
  • 要保证测试的完备性和可复用性,尽可能的做到团队级的复用。
  • 保证测试环境与生产环境的一致性也是测试驱动的重要环节。

提交规范

  • 每次提交尽量控制在2分钟可以给codereview的同事讲明白的程度,每次提交代码不能超过50行的代码。
  • 重构应该被当做一次专门的commit中完成,在commit中写清楚改动点和测试点,甚至对于某一个功能可以提交两次commit,第一次是正常完成的功能,第二次是单独重构后的commit。
  • 提交规范有助于定位bug,也是代码可读性的一个重要环节。

自动化测试

  • 构建可测试的软件,首先要构建可测试的环境
  • 对于简单应用软件可以使用单元测试,mock数据进行测试,并于ci、cd流程集成
  • 对于复杂应用软件可以采样收集线上真实用户行为日志,mock数据周期性巡检测试
  • 对于幂等性业务,可以mock user进行全方位的端到端的自动化巡检测试
  • 每一次功能的提交应该对应一套完整的自动化测试的策略脚本以及监控指标与报警规则

高质量上线

每次上线都必须具有上线计划,我的上下游业务是什么,发布上线单可追溯可排查问题,关注上线前和后的指标变化,正常来说这些指标都是没有变化的才是正确的。
上线单需要写明:改动点、风险点、止损方案、变更代码、相关负责上下游人员。

一些实际的问题

代码所有权

代码仓库的所有权会阻碍重构,调用方难以重构被调用方的代码(接口),进而导致自身重构的受阻,使得效率降低,为提高开发的效能,允许代码仓库在内部开源化,其他团队的工程师可以通过pr自己来实现代码,并提交给仓库的onwer,来code review即可。

没有时间重构

这是重构所面临最多的借口,是自己也是团队的借口。 为此必须要明确重构是经济行为而不是一种道德行为,重构使得开发效率变得更高,因此仅对必要的代码进行重构,某个工作行为如果重复三次就可以认为未来也会存在重复,因此通过重构使得下次工作更加高效,这是一种务实的作法,而重构不一定是需要大规模的展开的任务,重构应该是不断持续进行的,将任务拆解为多个具有完备性的任务,每周完成一个,每个任务的上线都不会引起问题,并使项目变得更好,这是一种持续重构的精神态度,是高效能程序员最应该具有的工作习惯。 如果你在给项目添加新的特性,发现当前的代码不能高效的完成这个任务,并且同样的任务出现三次以上,那么这时你应该先重构,再开发新特性。

重构导致bug

历史遗留的代码实在太多,难以阅读理解,如果无法理解谁也不敢轻易重构,害怕招致bug引起线上事故,因此在重构之前必须有一套相对完备的测试流程,他能给予程序员信心,也是重构的开始,反过来想对于谁也不愿意重构的代码进行重构,将收益巨大(这个项目还会继续迭代时)

参考资料

https://github.com/xianweics/refator-code
为什么我们的系统会如此复杂
重构

人未眠
工作数十年
脚步未曾歇,学习未曾停
乍回首
路程虽丰富,知识未记录
   借此博客,与之共进步