1. 背景
1.1 重构
Q:什么是重构?
重构是在不扭转软件可察看行为的前提下,改善其内部结构。–《重构 – 改善既有代码的设计》
Q:为什么要重构?
重构能够进步了解性和升高批改老本。–《重构 – 改善既有代码的设计》
Q:什么时候重构?
(1)何时不应该重构?
没有价值,没有意义或者投入产出比很低时。团队资源是无限的,无限的资源应该尽可能投入到有意义的事件下来。从团队的角度思考投入产出比,对于曾经只是保护状态,如无需要、无调整的代码,不要去动它,如果对于老手而言,不仅不会带来益处反而可能挖坑,要晓得既有代码可能有不少坑。
(2)何时应该重构?
- 我的项目保护老本很高
- 影响我的项目调优,如性能优化时
- 代码长得丑,不优雅时
- 既有设计和实现不利于扩大新性能时
- 重复性工作,既有的代码无奈帮忙你轻松增加新个性时
- 修补 bug 时,排查逻辑艰难
- code review 能够让别人来复审代码查看是否具备可读性,可了解性
- 太多的代码无正文,未然连本人都无奈疾速理清代码逻辑
1.2 如何重构
(1)筹备(基本功)
举荐值得一读再读经典书籍,重构圣经《重构 – 改善既有代码的设计》。自己从毕业第一年开始,几年下来读了 4 遍 +,受益匪浅,每次温习都能有所播种,让我常常折腾经手的我的项目却没出过问题。
(2)重构实际要点
- 思考分明(整体有设计,不肯定要文档化但须要想分明)。
- 协同布局(开发团队外部的配合及重构分支与其余分支的集成、内部资源提前申请如产品、测试、运维等)、整体规划。
- 分层分步开展,抓大放小从粗到细。善用“批处理”。
- 一次只做一件事。
- 不要反复造轮子。
- 当你感觉一件事很难的时候,停下来思考是不是办法用错了,它应该是怎么的。放弃监控及复盘本人的思考形式。
- 做好对内和对外沟通,尤其在当我的项目不是只有一个人在开发和保护的状况下。留神提前和相干方(测试、运维)沟通好(计划、次要工夫节点、须要投入的资源、须要其配合的事项)。
2. 社区 C 端的重构实际
本次重构具备肯定的复杂度,除了技术迁徙革新的老本外,波及的几个仓库是不同技术选型(框架 & 下层组件等)、我的项目疾速的麻利迭代、需要高并发及多人协同开发保护状态。
2.1 现状剖析
技术栈:
仓库名 | 技术栈 | 社区 C 端页面数 |
---|---|---|
repo A | React + umi3 | 指标仓库无需统计 |
repo B | React + umi3 | 5 |
repo C | vue2 + vuex | 27 |
我的项目侧
三个仓库 A / B / C 更新沉闷,每个仓库均波及多业务线的开发,并行保护。别离依照 2 周一个 sprint 的迭代节奏开展,1 周开发 1 周测试,间或穿插着 hotfix。
从 V1 主版本公布后开始重构,各个仓库波及的代码如下:
- repo A:A1 + A1. + A2 + A2.
- repo B:B1 + B1. + B2 + B2.
- repo C:C1 + C1. + C2 + C2.
.* 示意 hotfix
2.2 重构打算
前端侧的整体思路:
- repo A 较新,是社区的次要仓库,集中了大部分 C 端页面,作为指标 C 端代码的指标仓库。
- repo B 到 repo A:repo B 与 指标仓库的技术栈很靠近,波及 5 个页面,通过人肉形式迁徙,过程中留神依赖的一并迁徙。
-
repo C 到 repo A:repo C 与指标仓库差别较大,且语言异构,下层框架、组件库等都有较大差别,波及页面较多。
- 首先确定无效的页面,将已下线页面的 dead code 排除在迁徙范畴之外;具体细节下文会说到,取出待迁徙仓库中的前端路由配置,晓得页面总范畴,查看阿里云 sls 日志中近期的 PV(两种查问形式校对),排除无流量的页面。
- 分层分级重构,后期抓大放小,耗时耗力还容易出问题的框架语法转换(vue to react)应采纳脚本工具化实现,实现文件级和各个类中整体构造及援用关系的保护的转换。
- 细节语法通过自定义脚本批处理(比方 vue 中用的 class 的 key 和字符串模式的 value 转换成 react 中的 className 及变量模式的 value)。
- 为保障迁徙后高效自测须要将对应的 *.vue 文件保留,将其看成 doc 文件,待整个迁徙结束再删除,以晋升迁徙及测试的效率。留神革新 lint 规定漠视对这类文件的检测。
- 过程中依赖文件一起迁入,有“名称空间隔离”,留神放弃整体目录构造的绝对关系,做整体迁徙,且不去净化指标仓库中的既有文件,避免同名文件笼罩的状况。
- 通过上述三步将各个仓库代码迁徙到 repo A 后,同步 三个仓库中的最新更新。repo C 到 repo A 的过程中(从 V1 切出的分支),repo C 还在继续更新代码,repo A 还须要将 repo C 中的 V1.、V2、V2. 代码合入(repo B 亦然)。因为代码都在不同的仓库中,须要手工合并。Tips:能够在 repo C 中将 V1.、V2、V2. 的多个 commits 合成一个 commit,将所有变更项汇总到一处做批量更新。
- repo A 中 SSR 计划调研和利用也在并行。重构中新迁入的页面要和 SSR 做集成。
2.3 重构与集成实际
2.3.1 仓库 B 页面梳理及迁入
这部分迁徙在同构语言中进行,且波及页面数不多,次要通过人为迁徙。
2.3.2 仓库 C 页面梳理及迁入
-
线上流量查问,排除无用页面
- 三个代码仓库中路由申明确定总范畴
-
依据阿里云日志确定过来 3 个月、2 个月、1 个月中的 PV,将无 PV 的页面从待迁徙页面池中剔除。
- 留神 1: 阿里云 SLS 日志是基于上报的数据,上报和统计过程可能有丢数据的状况,所以综合两个查问入口确定和排查。
- 留神 2: 对于有 1-2 个 PV 的页面,可能是团队外部开发后期做调研时产生的,确定访问者后排出“测试”产生 PV 的页面。
- 确定最终重构范畴(27 个过滤 13 个)。将步骤 1 中获取的总范畴中在步骤 2 中无用户 PV 的页面剔除。
-
异构语言转换和解决
-
仓库 C 中 Vue2 转换为仓库 A 中的 react
- 工具转换
-
这里次要用到了 vue-to-react,然而该工具有不少束缚和限度,大略胜利转换了一半的代码,转化失败的状况须要本人写脚本实现。原想对该库的源码进行二次封装和革新,看了其实现发现定制的老本高于本人写脚本的老本所以弃了(自己 vue 的教训一个月不到),工夫太紧不容认真去钻研。Tips:防止反复造轮子,当执行很繁琐且很多反复的动作时,能够思考拥抱团队外部的轮子、社区和开源,没有的话就本人去倒腾一个。
- 脚本转换
-
转换
- 我的项目目录结构设计及文件的映射过程
// step1:放弃整体目录构造的相对性不变
.
├── apis
│ ├── community.ts
│ ├── h5community
│ ├── ...
├── components
├── pages
│ ├── h5community
│ │ ├── App
│ │ ├── api
│ │ ├── asset
│ │ ├── components
│ │ ├── config
│ │ ├── filter
│ │ ├── live.js
│ │ ├── main.js
│ │ ├── mixins.js
│ │ ├── router
│ │ ├── style
│ │ ├── utils
│ │ └── views
│ ├── community
├── utils
└── ...
// step2: foo.vue 文件转为 foo/ 目录,模板别离映射为 jsx 及 less 文件
.
├── apis
│ ├── community.ts
│ ├── h5community
│ └── ...
├── components
│ ├── h5community
│ └── ...
├── config
│ ├── h5community.js
│ └── ...
├── pages
│ ├── community
│ └── h5community
│ ├── column // 原 column.vue 转为目录,分拆成 index.tsx 及 index.scss
│ │ ├── index.local_js // index.local_js 作为正文保留,用于测试回归的参考
│ │ ├── index.scss
│ │ └── index.tsx // 首行主动插入对 index.scss 的援用
│ └── ...
└── utils
├── h5community
└── ...
- 分步转换 1: 文件级
对于 vue-to-react 解决失败的页面,通过脚本生成页面模版文件。
// 转换前文件为 foo.vue
// 转换后:.
└── foo
├── index.jsx
├── index.local_js
└── index.scss
自定义脚本转换生成的文件内容构造如下:
- 分步转换 2: 语法级 – html lang
Vue 文件转换过程中有很多 lang=”pug” 类的模版,通过工具 https://pughtml.com/ 转换成“类 jsx”的模版(凡是鸡肋人肉的事,首先应该想到工具,如果找不到,无妨 Google 中尝试用不同的关键词,而不要去人工)。
// 转换前 foor.vue 中
<template lang="pug">
article.modal-wrap(@touchmove.stop.prevent @click.stop='close')
section.modal
p.more 更多精彩内容, 就在得物 App
p.slogan 有毒的静止 x 潮流 x 好物
.enter-btn(@click.stop='enter') 进入得物 App
aside.close(@click.stop='close')
</template>
// 转换后 foo/index.jsx 中
<article class="modal-wrap" @touchmove.stop.prevent="@touchmove.stop.prevent" @click.stop="close">
<section class="modal">
<p class="more"> 更多精彩内容, 就在得物 App</p>
<p class="slogan"> 有毒的静止 x 潮流 x 好物 </p>
<div class="enter-btn" @click.stop="enter"> 进入得物 App</div>
<aside class="close" @click.stop="close"></aside>
</section>
</article>
- 分步转换 3: 语法级 – className 等
下面脚本生成的文件在于文件级的转换,语法差别须要脚本解决。比方 class 的替换和解析。这里 html 属性的规定解析正则比拟繁琐,实现时会思考哪里会有,很天然就想到了 vue 的源码中肯定会有该正则(框架是要解析做原生映射的),查了下果不其然,稍作批改就能够了,而后再做些定制(业务代码中的模版代码,如 import style 这些用脚本主动生成按需插入)。
// foo.vue 文件中的写法
<div class="var1">demo1</div>
<div class="var1 var2">demo1</div>
// foo/index.jsx(react 中)的写法
import style from './index.scss'
import classNames from 'classnames'
...
<div className={style["var1"]}>demo1</div>
<div className={classNames(style["var1"], style["var2"])}>demo1</div>
- 逐页面调试与校对
-
仓库技术选型间的差别问题
- umi 的路由规定与定制
- 第三方组件库
如 Swiper、postcss-px-to-viewport 等,vue 版与 react 版有些差别,文档不全,拥抱源码和社区。其中 postcss-px-to-viewport 在不同仓库中应用不同的 viewportWidth 设置,转换过程中通过对不同的插件实例解决不同的门路范畴实现
- 基本功:敏感度(这个跟教训无关)。库定位是什么?成熟度怎么样?应该有什么不应该反对什么?如果本人来设计大略会怎么设计(有时候即便文档不全状况下,不看源码也能够倒推出很多内容)?能够去哪里找解决方案?怎么找到?
-
迁徙 home 页配置
- 过程中放大 home 页的门路范畴,暗藏 repo A 中的拜访门路,仅透出待迁徙的门路,进步查找效率
- 迁徙过程记录(测试数据及门路等,不便穿插测试和 QA 回归)
- 覆盖度自测。一个页面中多业务逻辑的状况,后续须要对各门路进行足够自测
- 迁徙过程中目录和文件构造的设计与变动门路(重要)
2.3.3 集成 repo A、repo B、repo C 重构分支代码
- repo B 中的页面迁徙到 repo A 中,如用 chore-repoB 分支
- repo C 中的页面迁徙到 repo A 中,如用 chore-repoC 分支
- 将 repo A master 分支 和 chore-repoB、chore-repoC 合并并解决抵触,合并分支记为 chore-repoA-repoB-repoC,此时该分支仅有 V1 的代码,各个仓库以后版本的迭代性能和及上个版本的 hotfix 还未被合并入该分支。
2.3.4 集成 repo A、repo B、repo C 中迭代分支代码
主版本日前一天下午各个仓库中的迭代性能根本稳固,bug 曾经收敛。此时能够将该各个仓库的各个开发本地的分支 feat-foo、feat-bar 等汇总成一个 pre-release-temp 分支(已含有了 master 上的 hotfix),即 pre-release-temp 分支 是 V1.、V2 的汇总,将该分支的 增量 commits 合成一个 commit 获取 V1.、V2 影响到的文件变更。人为将这些变更同步到 repo A chore-repoA-repoB-repoC 分支上。
2.3.5 集成三个仓库业务代码与 SSR 代码
社区 C 端 SSR 革新计划确定后,新启了一个 A-SSR 仓库。应用 SSR POC 的框架内容对 A-SSR 仓库进行初始化,再将 repo A 中 chore-repoA-repoB-repoC 中的代码迁徙到该仓库中。遇到的问题:POC 中已对原 repo A 中的局部模块做了 SSR 转换,迁徙新代码到该仓库中留神文件笼罩代码失落,用 cp 而后 git diff 及人为 check 多变更源的文件后再提交。
待版本日中再将近 1 天 + 各仓库产生的 bugfix 同步到 A-SSR 仓库,确保代码无失落。
3. 我的项目推动之内部协同
3.1 测试
较大范畴的重构须要保障充沛测试,思考到占用的测试资源状况,尽可能提前和测试 leader 沟通资源需要。另外,移测前前端外部尽量充沛自测。
3.2 运维
提前打算好 页面重定向计划(将最终的跨仓库 / 利用迁徙的页面重定向),留神运维侧变更的影响,一旦做了变更,相干的在对应的测试环境就不可用了(QA 回归须要工夫,该过程中如果重定向启用了会影响该环境上相应页面的应用)。
3.3 遇到的问题
在开始布局及启动重构时,团队没有人对波及的所有三个 C 端仓库足够相熟。迁徙到第二个页时,发现有页面是没有线上流量的 dead code 时,从新沟通客户端及运维等同学,最终通过查问阿里云 sls 日志放大迁徙范畴,缩小了近一半的工作量。过程中遇到的各种技术问题,还是须要平时多做积攒。
4. 总结
简单我的项目的重构对研发的根底、教训、标准和各方协同有肯定要求。开始时能够多读几遍《重构》根底的打好了,逐步着手代码模块、简略我的项目、简单我的项目、跨团队简单我的项目等的重构,累计教训。事先做好布局(技术侧整体计划、技术方面的疑难病症提前预估、整体推动打算、相干方参加等),过程中思考全面足够仔细并继续复盘调整,过程后做好总结积淀。
事先做好设计、定期 Code Review、过程中和后续继续进行重构能够让我的项目代码具备更好的可维护性,团队放弃重构的习惯的同时一直积攒重构教训,能从整体上晋升我的项目的衰弱度与可维护性。重构看得见改善是要害,在重构中成长,在重构中受害,从重构中收益。
相干链接:
- https://pughtml.com/
* 文 / SHI FEI
关注得物技术,做最潮技术人!