当我们沉浸在旺盛的需求之中时,整个人便会成为一台工作的机器,切着类似的页面,写着同样的逻辑,重复着昨天或者上个月做的事情,时间久了,觉得腻味,没有什么创新,也没有明显的成长。用一句通俗的话来讲:工作五年,后面四年重复着第一年的活儿。
很多人尝试跳出这个怪圈,不过基于环境压力和思维受阻,最后又不得不选择放弃。今天想通过介绍如何高效有保障地开发一个无线页面来帮助大家找到突破口。
日常开发状态
很多无线页面的开发有两种模式,一种是后台输出 JSON 数据,前端根据数据来渲染页面(同步模式);第二种是前端异步加载后端数据然后渲染(异步模式)。当然,两种模式夹杂在一起也是存在的,这种情况一般会有一个由前端控制的中间层提供同步数据和异步数据。为了减少前后端的沟通成本,往往采用第二种模式。
拿到设计稿后,与后端同学约定接口格式,让后端同学尽快提供 mock 数据,如果提供不了,便自己构造测试数据。接着回到自己的工位上切图,切图过程中会解决好响应式问题和兼容性问题,待到后端产出真实数据时,更换 JS 中的接口地址,联调 ok 便发布页面,大功告成!
整个流程很顺畅,这对一个工作了三四年的程序员来说,没有任何压力便完成甚至提前完成了任务。但是,回过头来想一想,整个开发过程中我们留下了什么?沉淀了什么?
放慢节奏,我们再走一遍流程
对于上面的开发流程,先提出几个常见的问题:
- 你是如何良好处理大、中、小等各型号手机的适配问题的?Media Query?等比布局?
- 模块如何渲染,模板和接口数据如何拼装?字符串拼接?正则替换?模板引擎?
- 本地、预发和线上三套环境,如何进行无痕切换?
- 如果开发时接口有变动,线上数据暂未产出,本地 mock 接口如何快速响应?
- 如何解决异步 JSONP 接口的安全问题?JSONP 接口请求异常、超时、失败等情况如何处理?
- 页面中的 Slide 和 Tab 逻辑如何写?复制之前写过的代码?找一个好用的组件?
- 图片的懒加载处理如何控制?脚本的懒执行如何控制?
- 首屏加载页面空白体验如何优化?
- 页面回退 Session 和 Token 失效如何处理?
- …
上面提出的几个问题,列的不全面。有一些可能是你经常碰到的,甚至有了成熟的解决方案,而也有一些问题可能是你从未考虑过的。
我们把整个前端开发流程做简单切割:切图、获取数据、渲染、事件绑定、数据统计、页面优化、监控。这种切割很暴力,也比较粗糙,不过它不妨碍我们在下面讨论,作为前端工程师,除了完成日常需求外,还要做什么?还能做什么?
切图
隐约还记得三年前,我接了一个无线页面的外包活儿,页面的结构很简单,但我做的很糟糕。为了适配不同尺寸的机型,我写了无数 Media Query,加上当时采用的 em 作单位,很多细节位置都没控制好。
回到现在,已经有了很通用、主流的方案——使用 rem,动态计算 html 标签的 font-size
,思路很简单,但是存在不少的坑,和一些较难理解的概念,Google 搜索下 lib-flexible
能够找到这些问题以及解决方案。不过我们切图时还可以思考一些其他的问题:
- 各类静态资源(image/css/js/font)如何放置?新建各种文件夹?
- 是否还是修改代码再刷新页面的调试手段?考虑过 liveload?
- CSS 复用率如何?跨项目的复用率呢?使用预处理语言封装基类?
- 还在心算从 px 折算为 rem?用计算器算?
- 身旁放 20 台机器测试页面兼容性?
以上问题,没有哪一个会让人特别苦恼,但是堆积起来,却让我们的开发效率和开发体验落后了好几个档次。这些问题并非无解,我们可以尝试着帮助同事和团队找到问题的答案,比如:
- 统一团队的本地构建环境,初始化一个工程目录的脚手架
- 统一打包脚本,实时编译和预览
- 封装预处理基类,屏蔽 rem 计算,比如编译时自动转换 px 为 rem
- 构建云测平台,云端测试各种机型兼容性,打开网页输入网址即可批量测试
有些解决方案只需要几行脚本就能搞定,而有一些可能需要投入时间和精力。
获取数据
本地、预发、线上三套环境,如何做到环境的顺滑切换?我在百度的时候,团队最常用的方案就是:
- 线上测试,本地反向代理到预发或者线上环境;
- 本地测试,则使用 apache 开启服务提供 mock 接口
可一旦与后端约定的接口有变动,本地 mock 数据也要跟着一起变动。这个问题有什么好的处理方案?在团队中,好的方案一定不是几行文字的提示或指引,而是通过流程和监控来控制!
这里提到的获取数据,细想之下可不是什么轻松的事情。有很多问题需要思考:
- 如何保障 JSONP 数据的安全问题?refer 限制?token 验证?
- 数据来源很多,如何减少页面的请求数量?让后端合并数据?如果是多个团队提供数据呢?
- 如何控制需求变化导致的接口格式变化?
- 如何处理接口的不稳定问题?
- 如何处理超时问题?
- 如何产生容灾数据?如何获取容灾数据?
- 如何控制数据缓存?如前端控制缓存一分钟?
- 如何对接口做监控?
- 如何减少数据的重复请求问题?
以上每个问题都有很多处理方案,而这些问题不仅仅是自己会遇到,身边的同事也会遇到。如果可以站在团队的角度去思考问题,很多思路会比较容易涌现出来,比如:
- 构建一个平台,用于接口格式约定,通过约定好的格式,系统自动生成 mock 数据,用于本地开发,后端也必须遵循这个接口约定,任何接口的变动,mock 数据自动变动
- 构建一个平台,让不规范的数据进入这个平台,规范化输出,前端只考虑规范化的接口提示和解析,同时该平台产出数据的备份接口
- 前端添加一个请求 Hub,当页面有很多请求出来时,合并请求统一发出,当数据回来时,统一储存和过滤
数据是最容易出问题的地方,每一个接口请求都需要一大堆的逻辑处理异常。倘若接口格式、开发流程和前端模式都可以规范化,我们需要做的就剩下套公式,这种高效你能否想象?
渲染
大胆地揣测下大家在写一个模块的时候,跟我一样也是这么划分的函数:
var Module = function() { this.init(); }; // 初始化 Module.prototype.init = function() { this.fetchData(function() { // do something }); }; // 绑定事件 Module.prototype.bindEvent = function() { // ... }; // 获取数据 Module.prototype.fetchData = function(cb) { var self = this; ajax({}).then(function(data) { self.renderData(data); }).catch(function() { self._fetchDataFailed(); }).fin(function() { cb && cb(); }); }; // 渲染数据 Module.prototype.renderData = function(data) { data = this._resolveData(data); // ... this.bindEvent(); }; // 处理数据 Module.prototype._resolveData = function() { // ... }; // 加载失败 Module.prototype._fetchDataFailed = function() { // ... };
在代码中写大量的字符串模板不管一个模块有多么简单,它基本都会包含以上步骤,倘若没有用函数隔离每步操作的意图,代码会显得十分散乱。我经常看到,有同学把「渲染」这一块的代码被放到「获取数据」甚至是「初始化」中,这种程序结构显然是不合理的。同时,也经常会看到渲染时,
- 写一个工具函数,解析字符串模块中的循环逻辑
- 使用 replace 函数正则替换字符串变量
- 使用 innerHTML 函数插入拼装好的字符串
- 在渲染模块中添加大量逻辑
以上,没有哪一种是不正确的,我也没有对哪一种写法开喷的意图。但是至少我们可以在多次编程经验中提炼出一些有价值的内容:
- 团队统一的模板引擎,并且提供模块的离线编译,提高线上运行效率
- 提供安全机制,保障插入的数据不会产生安全问题
- 严格编程范式,分离视图和逻辑层,把数据处理好了再送入模板
有一个可执行的编码规范,加上适当合理的 Code Review,整个团队代码便会如出一辙。
后续操作
本想写成一篇长文,把每个环节可以综合考虑的问题都提出来,不过本文的目的,只是表述一些观点,期望大家在编程的时候,有更多基于团队的思考,针对具体问题提出一些通用的解决方案。比如下面,再提出几个问题:
- 首屏加载白屏问题,如何处理?本地缓存?等待提示?假数据?同步输出?
- 如何减少页面的请求?资源内敛?如何做到自动内敛所有的资源?
- 页面报错的统计如何做?做了之后如何分析?分析之后如何推动线上错误减少?
- 页面发布时如何自动回归检测?点下链接看看是否 404?打开控制台看看是否有报错?滚屏看看图片是否加载正确?
- …
以上问题,都有相当成熟的解决方案,那你们团队呢?
小结
当发现工作做起来索然无味的时候,我脑海中蹦出来的第一个念头是:最近是不是有点放纵了?
我喜欢用编程解决问题,只要是重复的事情,我一定会想尽办法简化,然后交给机器去做。我希望今年可以用程序解决更多的问题。
本文转自我的个人博客:http://www.barretlee.com/blog/2016/07/21/donnot-repeat-yourself/