每日一博 | 得物复杂 C 端项目的重构实践
1. 背景
1.1 项目背景
公司近两年快速发展,社区线C端代码分散在不同仓库中,每个仓库中采用不同的前端框架和选型,且均含有几条业务线的代码,团队整体采用敏捷模式快速迭代,导致开发管理成本较高,升级改造麻烦。比如,所关联的三个仓库中的代码均引了一个内部基础组件库,该组件有非必现bug,导致三个仓库的不同页面均出现了不同表现的异常,由具体负责的不同测试分别报到前端开发,分别沟通、排查、解决并走独立的发布上线流程,耗时耗力。当同一仓库中活跃着不同业务线的开发,一个公共的地方需要修改,开发没有沟通清楚导致冲突线上bug。
此外,公司C端体验分析的统计和报表是应用粒度的,先前代码耦合了其他业务的内容,导致我所在业务线的统计数据不置信。
近期团队对C端项目进行重构,将不同仓库中的代码汇总到一个仓库中管理。以期减少管理成本及方便后续对组内项目做优化和升级改造。
1.2 重构经验
之前我有独立负责过几次较大的重构,也曾2周独立完成近20万行C端代码(不含node_modules)从JS到TS迁移在并行业务需求迭代的情况下实现上线0bug。
1.3 重构基础
Q:什么是重构?
重构是在不改变软件可观察行为的前提下,改善其内部结构。--《重构 - 改善既有代码的设计》
Q:为什么要重构?
重构可以提高理解性和降低修改成本 。--《重构 - 改善既有代码的设计》
Q:什么时候重构?
(1)何时不应该重构?
没有价值,没有意义或者投入产出比很低时。团队资源是有限的,有限的资源应该尽可能投入到有意义的事情上去。从团队的角度考虑投入产出比,对于已经只是维护状态,如无需求、无调整的代码,不要去动它,如果对于新手而言,不仅不会带来好处反而可能挖坑,要知道既有代码可能有不少坑。
(2)何时应该重构?
-
- 项目维护成本很高
- 影响项目调优,如性能优化时
- 代码长得丑,不优雅时
- 既有设计和实现不利于扩展新功能时
- 重复性工作,既有的代码无法帮助你轻松添加新特性时
- 修补bug时,排查逻辑困难
- code review 可以让他人来复审代码检查是否具备可读性,可理解性
- 太多的代码无注释,已然连自己都无法快速理清代码逻辑
1.4 如何重构
(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/
*文/石菲
关注得物技术公众号~每周一三五晚18:30更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
2万人都在看的得物技术-Golang微服务沙龙PPT 合集来啦,
得物技术公众号后台回复「PPT」可以领取。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
LY-ADMIN-UI 首次开源了,免编译的后台前端框架
介绍 三年磨一剑,Ly-Admin-ui是我们经过多年内部维护和使用的ui框架,现在决定开源了 Ly-Admin-ui 好用超全的管理后台前端框架,它基于 vue + element-ui+vue-rap 技术栈。 它使用了最新的前端技术栈,项目在element-ui基础上又添加了超多实用组件和布局; 项目整体使用了最新的vue-rap流应用技术,项目可以不需要构建的情况下直接部署,边使用边下载。 演示地址http://lyadminui.magcloud.net/ 登录地址http://lyadminui.magcloud.net/portal/login 文档地址http://lyadminui.magcloud.net/ 特色功能组件 N种主布局只有搭配 支持菜单,配置项,文档搜索的全局搜索能力 支持拖动验证,文字点选等多种行为验证方式 好看的操作指引 支持drop,剪贴板等使用简单的文件上传 懒加载方式的弹出框,不需要将弹出框内的代码写到当前页面 一行代码的图表展示 超级强大的 table 组件和 table 相关的组件 完备的权限验证能力 还有很多,太多了... 安装方式 下...
- 下一篇
zuoyan-lens —— 设计稿 - 网页转换工具
zuoyan lens 是一个通过智能算法将设计稿转换为前端页面的产品(design to code),可以一键将 Sketch、Photoshop 的设计稿转换为可维护的前端代码。100个page的工作量10分钟内即可轻松搞定,极大释放前端生产力。 产品功能 生产级代码 通过智能算法推算出和手写代码一样的结构和css逻辑,产出的代码约等于一个中级前端的水平 全flex布局 根据元素所处的环境, 自动修正像素误差,符合设计表达。 代码可阅读、可维护. 智能切图 自动生成透明png切图, 不需要设计或开发手动切图导图 自动生成icon svg文件, 可直接上传到iconfont等作为字体图标使用,亦可转为svg雪碧 自动字体检测 自动检测设计稿字体,如果字体缺失会自动提示安装, 如果字体不一致会影响到页面还原度,不方便安装的字体,可以让设计师合并图层 循环布局识别 自动识别list,grid等布局方式 独有结点空间结构匹配算法, 能精确推算循环体,而且性能表现优异 跨平台,系统无关 兼容所有平台,windows和linux上也可以解析Sketch文件 设计师学习成本为0 只需要准守正常的...
相关文章
文章评论
共有0条评论来说两句吧...