写点什么

和祖传代码战斗到底:代码重构在阿里妈妈的落地实践

  • 2022-07-29
  • 本文字数:4200 字

    阅读完需:约 14 分钟

和祖传代码战斗到底:代码重构在阿里妈妈的落地实践

编辑|贾亚宁

嘉宾|冯雨


代码的优化与重构与每位开发者都密不可分,由于系统复杂度的提升、维护人数的增多、框架的升级等因素,代码的性能、质量往往会变得不可控。随着业务需求的不断变化和更新,代码也随着时间的推移变得越来越糟,可能会出现诸如重复的代码、过长的函数、冗赘类、过长的类等坏味道。


同时,代码的优化与重构话题也随着技术社区版本的更新,越来也成为了讨论的热点。比如如何高效解决从 Vue 2 升级到 Vue 3?是不断打补丁还是进行重构?重构过程繁杂,可以一次性开发个工具来解决重构问题吗?大型项目的重构相较于中小型项目又会遇到哪些特别的挑战?如何评估当前阶段是否适合进行重构升级呢?


为了寻找对以上挑战的最佳解决方案,我们特地采访了阿里妈妈资深前端工程师冯雨老师,他是阿里广告投放平台的前端 owner,负责过大型广告平台代码重构迁移工作。


GoGoCode (https://github.com/thx/gogocode)


冯雨老师也是开源代码转换工具 GoGoCode 团队的成员,他和他所在的阿里妈妈前端团队在迁移大型前端项目的过程中逐渐孵化出了这一工具,GoGoCode 上线后完成了十万行以上项目的前端框架升级,也为社区贡献了 Vue 2 升级到 Vue 3 的自动化升级方案。


同时,冯雨老师也在已经上线的 QCon+ 案例研习社代码优化与重构」专题中带来了《写个程序来帮你自动化重构代码》的实践分享。因此我们针对代码优化与重构方向的技术对冯雨老师进行了采访,让我们一起来看看他的实践和思考吧。

InfoQ:你最近在负责什么样的工作呢?


冯雨:我目前在阿里妈妈前端技术部,日常的工作自然是开发和维护阿里广告投放相关的前端工程,有 PC 端也有小程序。坦白说,我做的是大部分前端开发者都在做的工作,所以遇到的也是大家普遍会遇到的问题。


其中,我们有一个维护多年的大型项目想要整体升级前端框架,但是新的框架存在 breaking change,从模板到脚本都需要进行修改。如果人工来完成,工作量是很让人头大的,于是我们开始研究如何通过写个程序来进行重构,在此期间就做了 GoGoCode 这样一款代码转换工具。

InfoQ:你是如何理解代码优化和代码重构的呢?


冯雨:大家对于代码优化和代码重构可能都有着自己的定义,我先分享一下我的看法吧:代码优化就是让程序在功能不变的同时跑得更快;代码重构则是让程序在功能不变的同时理解起来更清晰、延展性更好。这两者其实有着共同的前提和不同的目的。


在企业项目中,对于代码性能优化,我认为可以在“不得不做”的时候去做。性能优化的代价有时候不仅仅是程序员的时间和发量,还可能是代码的可读性和未来的延展性,当性能不是瓶颈问题时做性能优化可能是弊大于利的。我甚至有几次对“代码优化”后理论性能更好的代码做重构,把它变成更简单的样子……卡农响起,这不是进入了循环么!


当你到了“不得不做”性能优化时,比如使用了特殊的数据结构或者并发地做了某些事情,我的经验是尽可能把这些属于计算机世界的操作丢到单独的地方,进行合理的封装和注释,这样我们的业务代码专注在描述业务的逻辑,而不是并发任务之间的调度。


对于代码重构,我建议在以下三种情况发生时就可以开动了:


  1. 当你把差不多的代码复制粘贴了 3~5 遍的时候这时候

一般意味着一些功能上的共性自然而然地显现了,不需要去提前假想,最佳的抽象时间就是它们站在你眼前的时候,我们把近似的功能抽取成函数和类,提高了可读性、减轻了书写负担和统一改动的麻烦。


  1. 当你对面前的一堆代码感到心烦意乱的时候这时候

可能需要一些分层设计了,就像上面对性能优化的建议一样,把属于计算机世界的代码和属于业务的代码分开,让自己脱离只见树木不见森林的困境;也有可能是代码里逻辑和数据的组织方式十分凌乱,这时只需要像整理房间一样打扫一遍即可。


  1. 当你的项目中使用的技术已经落后业界太多的时候

比如这几年我们看到很多人把基于 jQuery 的前端项目重构成基于 MVVM 的项目,获得了巨大的效率提升;Angular 1 到 Angular 2、Vue 2 到 Vue 3 也同时带来了好用的新特性和 breaking change。这种重构的特点是做起来工作量巨大且有大量重复的 API 适配工作,所以我们可以考虑引入自动化重构工具来帮助我们实现。


值得强调的是,重构从来不是一劳永逸的,互联网公司的项目是迭代速度非常快而且有时候是没有章法的。产品经理经常会提出超出之前抽象层次的需求,这导致以前的封装和分层设计可能会成为新功能的制约。如果只是应急,我们通过 hack 的手段打一个不优雅的补丁是可以容忍的(通常的做法是在通用的函数里加一个标记参数和分支),但是当这种补丁累积到再一次让你心烦意乱时,就可以考虑定义抽象的范围了。

InfoQ:你在进行代码重构的日常工作中,有没有什么印象深刻的踩坑经验吗?


冯雨:如果说是在日常工作中进行代码重构,我的经验来看,最容易被忽视但其实非常难搞是“人”的问题。程序终归是简单的,但每个人都有自己的思维方式和标准。


大部分的企业项目都是多人维护同一个仓库,避免不了互相阅读和修改别人的代码,维护共有的抽象和封装。当我们决定重构时,多半是认为当前的代码不够好,或者糟透了;同时大概率这段代码还不是自己写的,那在撸起袖子改动前,一定要提醒自己,这段代码不好可能只是自己的结论,最好提前和协作的同事们问个明白,也许有盘根错节的掣肘之处。


而且如果改动形成了新的抽象,也会影响到其他开发者之后的开发,无论是通过 code review 还是文档。所以一定要想办法告诉对方你的思路,如果我们的新设计在对方着急赶工时才被踩坑发现,相信我,无论它多么巧夺天工,也一定会被对方看做是一堆垃圾。

InfoQ:大型项目和中小型项目来比,代码重构有哪些明显的区别和难点,你一般是如何克服的呢?


冯雨:正如我母亲在收看春晚节目时说的:“我发现长的节目很难不臭,臭的节目很难不长,又臭又长是一种必然现象。”大型项目的“大”,让重构难度从量变升级到了质变。


首先,“大”会让你丧失重构的决心。比如就在去年,维护时的痛苦终于促使我们下定决心对团队维护的一个近 10 年、代码量超过 10w 行的老项目的底层框架进行了升级。


接下来面临的就是第二个问题,因为动了底层框架,所以这 10w 行大几百个文件我们要逐一改过,与此同时还不断有新的业务需要支持,因此这个改动不能一蹴而就。所以我们为新框架设立了一个全新的仓库,通过微前端的方案解决了老仓库老框架下引入新仓库新框架的页面问题,这样就可以渐进式地迁移了。


再到后面发现文件实在是太多了,为了加快效率,我们又决定试一试自动化的代码重构,因为框架升级主要是涉及 API 的变化,基于 AST 抽象语法树来写一个程序帮我们重构代码是可行的,这最终促成了我们的开源项目 GoGoCode 的诞生。


还需要注意的是,一个大项目,大概率是比较重要的项目,所以对稳定性的要求会非常高。如果条件允许,在重构前完善单元测试会让这个过程信心倍增。我们还做了很长时间的灰度测试,慢慢把重构后的代码推给用户使用。如果你的公司 / 部门有专业的测试团队,可以考虑搭上一些项目的顺风车,优先重构有项目的模块,这样就能得到比较完善的回归测试了。

InfoQ:你刚才提到的 GoGoCode 相较于其他的代码转换工具有什么亮点?目前阶段还有什么缺憾?


冯雨:在最开始有这样自动化重构需求的时候,我们原本打算直接使用社区已经较为成熟的 Babel 或者 jscodeshift 去做,这些项目基于 AST 带来了操作的精确性。但是在使用过程中,想要写一个很小的功能都需要走一遍繁杂的 AST 查找、构造和修改的过程,而且之后的可读性也比较差。


此时我们联想到平时批量修改代码最常用的其实是全局的“查找 / 替换”,还有用 CSS 选择器查询 DOM 树的 jQuery 语法,如果把二者结合,能不能用更加直观的字符串来构造一个“代码选择器”去查询 AST 里的节点,通过通配符的形式去捕获我们需要的 AST 结构。这样岂不是就能像“查找 / 替换”一样去编写代码修改程序了?


我们根据这样的设想实现了代码转换工具 GoGoCode,写转换程序的效率就大幅度提升了。比如,假设你想在下面代码中挑选出名为 log 的函数:


function log(a) {  console.log(a);}
function alert(a) { alert(a);}
复制代码


只需要按照如下方式使用 find 方法即可:


const ast = $(source);const test1 = ast.find('function log() {}');
复制代码


如果使用通配符,我们能匹配到所有名字的函数定义,我们提供 each 方法来遍历这个结果集合,下面的例子把 match 到的函数名收集到了名为 names 的数组里:


const fns = ast.find(`function $_$0() {}`);const names = [];fns.each((fnNode) => {  const fnName = fnNode.match[0][0].value;  names.push(fnName);});
复制代码


如果我们想把 log 函数改名成 record,用 replace 做会非常简单:


ast.replace('function log($$$0) { $$$1 }', 'function record($$$0) { $$$1 }');
复制代码


这看起来就像字符串的替换,但底层实现上是基于 AST 的替换,所以无视了代码的结构,也可以随时对 AST 节点进行高级操作。


经过几个大规模项目的实验,GoGoCode 已经足够健壮,于是我们就把它给开源出去了,也获得了不少 star 和社区反馈。这之后我们又做了 Vue 2 到 Vue 3 的转换插件,也帮助到很多社区小伙伴进行了框架升级。


要说缺点,我们的生态和稳定程度显然是不能和 Babel 这样的底层设施相比的,我觉得 Babel 是更适合大家去被动使用的,而 GoGoCode 可能会让你有兴趣亲自写一个代码转换程序。

InfoQ:最后,作为一名在大厂工作的从业者,你想对 QCon+ 案例研习社的用户们说些什么呢?


冯雨:理想中的大厂前端应该总是站在前端之巅挥舞着最新的技术,但其实大部分的白天黑夜,我们只是伏在一座座代码山下卖力挖掘,这些项目看起来可能不是那样精致,却不停地奔跑在数以万计的计算机上去帮助海量用户。所以除了新鲜的技术,我也会格外重视这些每天看守的项目,想办法让它们变得更好。就像我们今天一直在聊的重构经历,在让项目变好的同时,我们也在让自己的水平变得更好。


在这个过程中,如果你能形成一种经验,千万不要吝惜,把它先通过一些线下交流分享给你的同事们,或是记录成文章分享到互联网上;更可以“码以载道”,通过一个开源项目帮助更多人,这样你也会收获别人对你的认可。


欢迎关注开源项目:GoGoCode


如果你对阿里妈妈前端技术部感兴趣,欢迎加入我们,联系方式:chibing.fy@alibaba-inc.com


嘉宾简介


冯雨 阿里巴巴阿里妈妈 营销研究和体验中心资深前端工程师


阿里广告投放平台前端 owner,开源项目 GoGoCode 团队成员,负责过大型广告平台代码重构迁移工作。

2022-07-29 11:328530

评论

发布
暂无评论
发现更多内容

怎样区分试验与仿真的关系?

思茂信息

仿真软件 仿真技术

【羊城晚报】WeOps智慧护航,传媒“领头羊”业务迈向新高度

嘉为蓝鲸

IT运维 传媒 传媒公司

中企出海成大热趋势,海外用户如何高效触达

MobTech袤博科技

如何保障医疗机器人的功能与安全?这几条编码标准你一定要了解

龙智—DevSecOps解决方案

医疗机器人 编码标准

打卡有礼!快来 2023 开放原子全球开源峰会找龙蜥玩~

OpenAnolis小助手

开源 操作系统 龙蜥社区 开放原子全球开源峰会 龙蜥实验室

NFT全链游戏dapp系统开发合约定制

开发v-hkkf5566

Vue.js 最佳实践:提高性能和减少耦合的方法

xfgg

JavaScript Vue 前端 6 月 优质更文活动

嘉为蓝鲸研运一体化解决方案入选“鑫智奖”

嘉为蓝鲸

智能硬件 蓝鲸 金融数据

10个刚需的Blender小技巧

Finovy Cloud

blender C4D

牛客网论坛最具争议的Linux内核成神笔记,GitHub已下载量已过百万

简说Linux内核

Linux内核 嵌入式开发 设备驱动 内存调优 内核组件

做开发5年,这8个高效开发好习惯我悟了🔥

引迈信息

程序员 前端 低代码 JNPF

想让ChatGPT和低代码开发实现完美结合?看这篇文章就行!

加入高科技仿生人

低代码 数字化 ChatGPT

软件测试/测试开发丨Allure2报告中添加附件-图片

测试人

程序员 软件测试 测试开发 Allure

几个小技巧,提高你的代码质量

SoFlu软件机器人

代码质量 程序员、 软件开发、

PAG动效框架源码笔记 (五)渲染流程

olinone

ios android 动效 渲染

提升效率:P4VFS让虚拟文件同步更迅速、更简单

龙智—DevSecOps解决方案

文件同步 虚拟文件同步 Virtual File Sync

2023上海国际嵌入式展 | 如何通过人工智能驱动的自动化测试工具提升嵌入式开发效率

龙智—DevSecOps解决方案

嵌入式 嵌入式软件 嵌入式设计 嵌入式开发

TDengine 合作伙伴 +1,这次是「DaoCloud道客」

爱倒腾的程序员

涛思数据 时序数据库 ​TDengine

直播倒计时1天 | 一体化智能可观测平台如何保障电商节大促

博睿数据

电商 智能运维 博睿数据 直播预告

备战金九银十:大厂面试官必问MySQL连环炮全梳理,你扛得住嘛?

程序员小毕

Java MySQL 数据库 程序员 面试

运维人员福音!自定义插件为运维提供更多可能

嘉为蓝鲸

#运维 Python运维 Linux 运维

下载量破 15000!龙蜥社区登陆阿里云 ACR 制品中心 TOP5 榜单

OpenAnolis小助手

阿里云 操作系统 容器镜像 龙蜥社区 Dragonwell

迈向新时代的英特尔代工服务:走差异化路径,坚持客户至上

最新动态

一篇关于代码质量的实用攻略!

SoFlu软件机器人

代码质量 软件开发、

软件测试丨Allure2报告中添加用例支持tags标签、失败重试功能

测试人

程序员 软件测试 测试开发 测试用例 Allure

国外云主机:为你的业务提供全球级托管!

一只扑棱蛾子

云主机

揭秘新一代云数仓技术架构与最佳实践

字节跳动数据平台

大数据 数据仓库 云原生 OLAP 数据仓库服务

有哪些内外网都能传输文件的工具-镭速

镭速

看过才知道,这套SpringCloudAlibaba笔记,把微服务玩的出神入化!

程序知音

Java 微服务 SpringCloud java架构 后端技术

聚焦 AIGC,函数计算为 AI 应用插上腾飞翅膀

Serverless Devs

Serverless FC AIGC

和祖传代码战斗到底:代码重构在阿里妈妈的落地实践_QCon+_Alice_InfoQ精选文章