2016 回顾 -- H5 项目重构总结 | 掘金技术征文

1,560 阅读10分钟

重构的缘由

易企秀(www.eqxiu.com/)在2015年12月, 我入职的时候, 整个项目是一个大型的单页应用. 包括了首页, 作品列表页, 编辑器, 作品详情, 秀场等. 由于业务的急剧膨胀, 大项目组已经无法满足敏捷开发的需要, 产品会变得冗长低效, 产品层面上整个主站需要进行剥离, 工程项目的拆分及重构也势在必行. 易企秀的核心业务是 H5编辑和预览. 由于迭代迅速, 功能往往以实现为主, 缺乏设计, 代码略显混乱. 前端组认为为了未来支持更为丰富的组件和编辑功能, 需要对预览还有编辑器项目进行一次’重构’.

image
(上图: 场景预览)

image
(上图: 场景编辑)

回头看这次决定, 出发点是非常好的, 但是笔者认为重构的安排略显仓促.

一, 项目的拆分, 重构和优化是不应该一起进行的. 三者想要达到的目的各不相同. 而最初的目的是不清晰的, 只是希望完整实现一个更好的版本, 这样势必导致重构人员会发散, 最终的结果是重写了整个项目… 造成修改的代价及其高昂, 而且开发周期会很长, 测试的压力会很大.

二, 项目重构的前提是对项目有了充足的了解, 但是当初无论是文远还是我都对核心业务知之甚少(作为当初资历最浅的两个人且无大型项目重构经验). 所以在重构的整个过程中, 对业务理解所花费的时间非常多, 需要频繁的与同事进行交流, 不可避免的会出现很多理解的偏差, 也造成了很多业务逻辑上的 bug.

不过, 重构的大幕在2016年3月正式开启了.

阶段

为了好理解, 先将 H5预览项目称之为 view 项目, 将编辑器项目称之为 editor 项目.

整个重构分成5个阶段

  1. view 项目核心功能拆分, 技术选型及实践: 面向对象思想的第一次验证, ES6, gulp 引进, 第一版实现所有功能
  2. view 灰度及 bug 修复
  3. 编辑器功能边界划分, view 项目模块化改造为 core 项目
  4. 编辑器项目核心框架搭建完成, 多人参与, 完成90%功能重构
  5. 编辑器灰度, 重构工作全部完成
  6. 合并 view, core 项目 (未来工作)

第一阶段: 技术选型及实践

文远在一开始进行核心功能重写时, 选择了 OO 进行重构, 经过讨论, 我们认为 OO 非常适合 H5 多组件的情形. 非常方便未来扩展新的功能. 而且由于 view 项目是一个非常独立的项目, 所以我们希望其不依赖任何框架, 这样才有机会融入到其他框架中去(当时的情况是编辑器是用 ng1.2, 并且当时认为可能会出现手机编辑器等多种 H5编辑器).

在讨论到 jQuery 的时候, 由于项目中大量使用了 jQuery 的选择器等语法也使用了 jQuery 的相关插件, 从开发工作量上考虑, 我们决定保留.

在是否使用 ES6 语法上, 团队产生了较大分歧, 笔者当时更为激进, 希望采用 typescript 来进行编写, 理由是 view 项目将作为种子项目, 为未来的多编辑器进行服务, 希望保证质量, 团队其他成员认为 ts 或者 es6 的使用增加了学习成本, 最后妥协的结果是我们选择了部分 es6 语法. 对于这个妥协, 我个人是满意的, 事实证明, es6 在当前前端开发中大行其道, 应该会在未来的开发中成为默认语言.

由于当初笔者正好对项目组分享了 gulp4.0(slides.com/xy2/gulp-1#…) 的使用, (当初项目组还在使用 grunt 进行构建), 所以我们采用了 gulp4.0 作为构建工具.

我们决定将 less 转化成 sass.

在是否使用模块化开发问题上, 团队也发生了分歧, 由于当初没有人有模块化开发的经验, 所以模块化开发的提议被否决了, 一个理由是当初的方案就是采用挂载全局变量的方式, 比较好理解, 不过当初全局变量非常随意混乱. 最后的结果是 项目使用命名空间的形式, 将所有的组件都按照功能挂载到 EQX 这样一个全局变量上. 最终形成了 EQX.init, EQX.util, EQX.const, EQX.API, EQX.HOST, EQX.tpl, EQX.effect 等子模块, 场景类, 组件类, 辅助类等则直接挂载到 EQX上.

image

(上图为EQX全局变量的部分属性)

我们设计了大量的辅助类来解决复杂问题, 例如 EQX.formManager(处理表单提交逻辑), commentManager(处理表单逻辑), bgm(处理背景音乐), pageScroll(处理翻页逻辑), progressbar(处理进度条逻辑) 等

除此之外,

为了避免在业务逻辑中兼容各式各样的老数据, 我们增加了一层数据改造层(adapter), 来集中对发现的老数据, 坏数据做一次转换(但是因为对项目本身了解不深, 在改造数据时, 无法预计到所有的数据结构, 所以导致改造逻辑简单, 无法真正做到所有数据的兼容, 甚至改造错误), 减轻业务逻辑.

我们统一了异步处理的方式: 全部使用 Promise, 原则上不再使用回调函数.

我们规范了初始化场景的逻辑, 分为以下步骤

  1. 后端渲染场景数据
  2. 前端根据页面来源区分 H5预览, APP 应用内预览, PC 预览, 是否需要密码登录,初始化页面布局 或者进行微信授权等操作.
  3. 获得页面数据(异步获取尾页, 广告页等). adapter 进行数据改造(更换微信用户数据等)
  4. 页面渲染

对整个 view 项目中出现的业务概念我们做了如下抽象.

EqxScene(场景)
EqxPage(页面), EqxLongPage(长页), EqxXiuPage(秀版, 广告页)…
EqxBackground(背景)
EqxBgm(背景音乐)
EqxComp(组件)
EqxText, EqxImage, EqxInput, EqxInputPhone...
EqxPageEffect(页面特效)
EqxSnowEffect ...
EqxPageScroll(翻页器)
book, card ... (采用策略模式, 抽象出不同的翻页算法)
EqxProgressBar(进度条)
EqxManager(辅助类)
EqxEventManager, EqxSoundManager, EqxStatManager, EqxCommentManager, EqxFormManager
EqxAgent
EqxSoundAgent…

大致关系图如下.

image

按照这样的规划, 我们完成了第一个里程碑, 实现了现有功能的全部改造.

灰度及 bug 修复

由于 view 项目是公司最核心的项目, 我们经过广泛讨论确定了逐步灰度的方案: 按照场景发布时间进行划分逐步进行灰度, 且灰度策略随时进行调整.

在历时一个月的时间内, 我们累计处理了几百个 bug. 这也反映出对业务理解不深. 不过感谢测试团队, 也感谢我们的项目管理方式(jira), 可以让我们有序的进行 bug 修改.

编辑器功能边界划分及模块化实践

在灰度进行中期, 笔者开始了第三阶段的开发, H5编辑器的拆分 和 view 项目的模块化改造, 升级成 core (核心组件库) 项目

笔者在初期定下了如下重构理念

  1. 数据驱动
  2. 依赖组件库.
    为了保证编辑器和预览时组件渲染一致, 编辑器将所有和渲染相关的逻辑全部交由组件库.
  3. 全指令 + 模块化
    笔者考虑到未来的团队合作, 希望尽早开始模块化的尝试, 参考了以下文章
    xufei/blog#29 Angular 1.x和ES6的结合
    kuitos/kuitos.github.io#34 Angular1.x + ES6
    笔者决定, 升级 ng1.2 至 1.5. 整个项目采用 ES6 + (webpack + gulp)+ 全指令的形式进行模块化的重构.
  4. service 做到 简易统一的 API; 职责单一; 少依赖; 可复用
  5. 编辑区鼠标操作全部重写

经过项目拆分, 笔者划定了项目的整个边界
包括 模板, 素材, 页面管理, 编辑, 场景设置, 设计师功能等.
其中涉及到编辑组件的功能边界如下
image

在第四个阶段, 笔者负责了搭建整个项目框架 (保证项目可以最小化运行 + 核心编辑能力) 基本框架如下
image

编辑区基本框架如下
qq20170116-3

随后, 我们按照模块化的方式进行了项目划分和开发. 一个典型的模块及子模块(样式编辑-触发)的代码组织如下
qq20170116-4

经过近两月的努力, 我们小组(四人)完成了编辑器的重构工作, 随后进行了第二次灰度和 bug 修改(又是上百个 bug…..), 这次我们采用了用户注册时间和权限的策略进行灰度.

code review 实践

在这段时间, 组内成员进行了 code review 实践, 效果比较好. 特此记录.

笔者认为 code review 是一个集合了知晓同伴工作, 协调工作进度, 讲解代码, 提高团队成员水平, 统一风格等........诸多好处的软件开发辅助活动...... code review 是一种形式, 绝不是目的.

在实践中, 我们每天至少花一小时的时间进行代码交流. 鼓励团队成员每天进行有意义的代码提交 (至少1-2次), 这样可以及时的 review, 及时的给出建议, 逐步改善编码习惯. 具体在 code review 进行过程中

  1. 每个人简要展示可交付的内容
  2. 选择性对代码进行讲解
  3. 针对代码中的问题进行分析和讲解, 如果能带出一些知识点来, 可以进行展开
  4. 对明日的工作讨论, 如果有技术上的难点可以和大家探讨交流.
    如果功能确实比较少, 那么 code review 会主要以技术分享为主. 分享的内容包括但不局限于 angular 的各种中高级用法, Promise, webpack, 设计模式, 软件构建的优秀实践等.

毕竟团队开发, 大家好才是真的好 :D

现在重构已经到一段落. 不过现在仍然存在 view, core, editor 三个项目. 目前 core 项目已经非常稳定的工作, 提供渲染和编辑场景的内部逻辑. core 项目目前支持 PC 编辑器, 广告项目场景渲染 以及 安卓混合模式的开发.

下一阶段需要将 core, view 进行代码的合并形成真正意义上的 核心组件库.

总结

经过这次历时接近一年的’重构’, 笔者认为既有良好的实践, 也有诸多可以改进的地方.

  1. 在项目初期确定了技术选型(ES6, OO, 模块化等), 编码规范等, 保证了开发过程中大方向的一致性.
  2. 需要感谢团队协作和敏捷实践, 可以让团队保持较好的开发节奏
  3. 需要感谢测试人员的持续跟进, 灰度后项目虽有 bug, 但基本都在掌握之中
  4. 当然最需要感谢的是重构团队兢兢业业, 任劳任怨 :D, 虽然加班是常态, 但是长期的 code review 实践, 对于笔者和团队在技术和协作上都有很大的提高.

当然, 总结重构, 我们也有很多不足:

  1. 前期对于重构的定位不清, 重构没有时刻表, 整体进度不好把握.
  2. 重构前对重构项目的业务了解不够深入
  3. 重构前对重构项目的数据结构了解不全
  4. 成员对 OO 及设计模式, NG 中高级概念 的理解不深, 打铁还需自身硬啊!

对于最后的重构结果笔者是较为满意的, 运行了近两个月的时间, 在新需求的开发过程中, 由于前期的顶层设计, 每一个组件都’理所当然’有他的位置, 整个架构很好的支撑了业务的发展. 笔者希望, 未来的重构最好不要伤筋动骨而应该是渐进增强. 总之, 感谢这次经历 :D