性能
凡是真正有价值的性能优化,必定是从端到端的业务场景建立体系来考虑的。
性能体系的建立可以分成以下几部分:
- 现状评估和建立指标;
- 技术方案;
- 执行;
- 结果评估和监控。
1. 现状评估和建立指标
要想做好性能优化,正确地评估现状和建立指标是最关键的一步。
作为一个工程师,指标又要考虑两个因素:
- 对用户来说,什么样的性能指标能更好地评估它的体验?
- 对公司来说,什么样的指标会影响业务价值呢?
性能问题可以分成很多方面,最重要的几个点是:
- 页面加载性能;
- 动画与操作性能;
- 内存、电量消耗。
注意,这里我们仅仅是对“性能”两个字的分析和解读,在对大量的用户数据分析后,我们发现,其实这三部分中,“页面加载性能”跟用户的流失率有非常强的关联性,而用户流失率,正是公司业务非常看重的指标。
因此,在开始阶段,我们决定把性能优化的重点放在页面加载性能上。
那么,用什么指标来衡量页面加载性能呢?最容易想到的方案是“用户平均加载时间”,事实上,我们在相当长的一段时间,也都是在使用用户平均加载时间作为性能指标。
但是,很快我们发现,这个指标有严重的问题:
- 当加载时间低于一定数字,用户体感差别不大了,我们经过一定的研究,认为这个数字大约是 1 秒;
- 少数超长时间加载的用户(如 2G),会极大影响整个指标,即指标不能反映大多数用户的体验。
于是,基于以上分析,我们设计了一个新的指标——秒开率,即一秒之内打开的用户占用户总量的百分比。
2. 技术方案
有了指标,我们就有了优化的目标,就到了技术出场的环节了。
以加载过程为例,来讲解一下。首先我们要简单分析一下,从输入 URL 后按下回车,到底发生了什么。我们必须理解几件事:
- 从域名到 IP 地址,需要用 DNS 协议查询;
- HTTP 协议是用 TCP 传输的,所以会有 TCP 建立连接过程;
- 如果使用 HTTPS,还有 HTTPS 交换证书;
- 每个网页还有图片等请求。
最终设计的技术方案大约可以这样划分:
这里涉及的并不仅仅是前端技术,有服务端、客户端、设计师团队,所以要想做好性能优化,绝对不能把自己限制在局部的视角,必须是整个业务一起考虑,才能有良好的收效。
3. 执行
执行也不简单,如果说方案主要靠技术,那么执行就是靠工程实施了。
根据公司的实际情况,工程实施可能有不同的程度,我把工程水平从低到高分成三个阶段:
-
纯管理;
纯行政管理,是由经理用纯粹的管理手段来执行方案,比如说,作为前端团队的 Leader,可以组织会议,要求整个团队使用我们前面谈的技术方案。但是纯行政管理有一些问题,一方面,需要的行政资源不一定有,比如我没法强制让后端团队配合我,另一方面,纯粹的管理方式,团队本身的体验并不好,也不利于团队成长,最重要的是,纯粹管理方式容易造成执行不到位。这样的执行方式多数出现在非技术岗位。
-
制度化;
制度化执行方式是用规则代替人的命令,指定责任人,通过培训、checklist、定期 review 等具体措施来保证实施。制度化执行可以极大地减轻管理工作量,一般现代互联网公司都会采用类似的方式。但是制度化执行方式还有很大成分是依靠人的主动性的,对程序员来说,还有更好的方式:自动化。
-
自动化。
自动化的方式是在一些重要的操作路径上设置规则,针对我们的性能优化,有两个点适合做这件事:一个是把开发好的页面发布上线,另一个是开发好的页面 URL投放到首页等处的链接。
4. 结果评估和监控
做线上监控,分两个部分:
- 数据采集;
- 数据展现。
数据采集部分,同样需要发布平台或者开发工具来配合,对性能数据来说,Performance API 非常好用,它是浏览器记录的性能数据,一般来说,我们用统一的代码把它上传到服务器端就够用了。
数据的展现部分就比较自由了,可以用不同的数据可视化方案来展现性能数据,没有一定之规。一般的数据监控平台,会提供报警机制,对性能来说,报警需求不是特别强烈,但是也可以设置一些条件,针对秒开率特别低的网页报警。
有了监控,再配合一定制度,就可以保障整个团队产出的性能了,要注意,性能不是一个静态的事情,指标需要不断优化,技术方案还需要不断随着技术发展迭代,制度、自动化工具也需要不断改进,最终的监控平台产品也不能不做新需求,所以性能应该成为一个团队的日常工作的一部分,持续进行。
工具链
考虑工具的时候同样要遵循基本规则:现状与指标、方案、实施、结果和监控。
1. 工具体系的目标
工具链是一系列互相配合的工具,能够协作完成开发任务
工具体系的“元问题”,即:我们对工具本身的要求是什么?
-
考虑到工程行为都是团队合作,我们对工具最基本的要求就是:版本一致。只有整个团队的工具版本一致,至少要做到避免大版本差异,才能做到互相接手代码时,团队成员能够正确的使用工具开发。
-
工具体系的另一个重要需求是:避免冲突。一些工具可能互相没有干扰,比如 Yeoman 和 gulp,有一些工具则由社区设计了配合方案,比如 webpack 和 babel,有一些工具,则存在着根本性冲突,如 gulp 和 grunt。
2. 工具体系的设计
要想设计一个工具链,需要整理一下前端开发大约要做哪些事:
- 初始化项目;
- 运行和调试;
- 测试(单元测试);
- 发布。
那么,一个前端项目的工具链,大约就会包含这些功能。一个典型的社区项目工具链可能就类似下面这样:
- Yeoman
- webpack
- ava/nyc
- aws-cli
但是,这显然不够,我们还需要一种机制,保证团队使用的工具版本一致:
- 轻量级的做法是,在项目初始化模板中定义 npm script 并且在 npm dev-dependency 中规定它的版本号。
- 重量级的做法是,开发一个包装工具,在命令行中不直接使用命令,而使用包装过的命令。
同时,统一的命令行入口,意味着整个团队不需要互相学习工具链,就可以接手别人的项目开发。
在稍微大一些的团队内部,往往会需要不止一种开发模式,如移动开发和桌面开发,这样,所需要的工具链也不一样,因此我们需要多条工具链。
3. 工具体系的执行
工具体系的结果虽然是软性的,也不能完全不做监控。
纯粹的社区方案比较难做到监控,但是如果我们使用了前面提到的统一命令行入口包装,那么就可以做一些简单的统计工作了。
一般来说,以下指标跟开发者体验较为相关:
- 调试 / 构建次数;
- 构建平均时长;
- 使用的工具版本;
- 发布次数。
持续集成
持续集成:是指在软件开发过程中,以定期或者实时的方式,集成所有人的工作成果,做统一的构建和测试。
与持续集成相对的做法是:独立开发各个模块,在软件开发的最终阶段才做集成。
持续集成的优势是及早处理集成阶段的问题,使软件质量和开发进度可控。
1. 持续集成总论
传统软件的持续集成主要有以下措施。
- daily build:每日构建,开发者每天提交代码到代码仓库,构建一个可运行的版本。
- build verification test(BVT):构建验证测试,每日构建版本出来后,运行一组自动化的测试用例,保证基本功能可用。
对于前端来说,有一些现实的区别:
- 前端代码按页面自然解耦,大部分页面都是单人开发;
- 前端构建逻辑简单,一般开发阶段都保证构建成功,不需要构建;
- 前端代码一般用于开发界面,测试自动化成本极高;
- 前端页面跳转,是基于 url,没有明确的产品边界。
基于以上分析,传统的持续集成方案放在前端,要么不需要,要么不适用,要么实施成本高,因此我们不能套用传统的持续集成理论,而需要重新思考前端领域的持续集成体系。
2. 持续集成的目标
每日构建不需要,前端构建验证测试成本过高难以实施,那么我们是不是可以有一些代替的措施呢?
首先我们要确定前端持续集成的目标,我们回到持续集成的根本理念,
- 一是要及早集成代码形成可测试的版本,
- 二是通过一定的测试来验证提交的代码的有效性。
3. 持续集成的方案
前端的持续集成的措施应该是这样的:
- 预览环境,代替每日构建,前端每次(或指定次)提交代码到仓库都同步到预览环境,保证预览环境总是可用;
- 规则校验,代替构建验证测试,通过数据采集(如前面提到的性能数据)和代码扫描,保证提交的代码满足一定的质量要求。
预览环境
前端代码发布到线上生产环境需要有线上的机器和域名,而预览环境同样需要机器和域名,不过,只需要在公司内网即可
所以建立预览环境的第一步就是申请机器和域名,我们需要运维协助,在预览环境的机器上部署 Web 应用服务器。
有了预览环境的机器,下一步就是建立预览环境发布机制。预览环境的机器发布流程应该跟线上发布保持一致,这样可以最大程度降低成本和降低心智负担。
规则校验
规则校验可以分成三种措施:
-
页面结构扫描;
页面结构扫描可以使用无头浏览器(如 phantomjs)配合一些 JavaScript 代码编写的规则来完成。
-
运行时数据采集;
运行时数据采集,可以通过在页面插入公共 js 文件的方式来完成,最基本的是用 Performance API 来采集性能数据,用 window.onerror 来采集 js 错误。
-
代码扫描。
代码扫描,社区有一些现成的方案,比如 JSHint,你可以根据实际需要,选择社区方案或者自研。
4. 持续集成的实施
持续集成的实施,是必须严格做到自动化和制度化的。
5. 持续集成的结果
持续集成机制的建立本身就可以视为一种结果,它能够让整个团队的代码质量有一个基本的保障,提前发现问题,统一代码风格,从而带来开发体验和效率的提升。
此外,持续集成的结果也能够以数据的方式呈现出整个开发团队的健康状态,这是管理者会非常关注的一个点。
前端架构
绕前端架构的几个核心问题:
1. 组件化
现行的组件化方案,目前有五种主流选择:
-
Web Component;
Web Component 是 W3C 推行的规范,理论上是未来的选项;但是实际上这份标准的状态堪忧,Shadow DOM 的设计比较复杂,一般的前端掌握起来都比较困难。
-
Vue;
Vue ,由华人程序员尤小右开发和维护。它有两个主要特点,一个是比较符合原本的JS/CSS/HTML 书写习惯;另一个是它绑定了 MVVM 模式,直接确定了 UI 架构,通过 DSL 的支持,数据交互非常简洁。
-
React;
是 Facebook 推行的新一代 Web 框架。它利用 JSX 模式,把 HTML、CSS 和 JS 都放进了 JS 文件中,对于不喜欢 CSS 和 HTML 的前端工程师来说,是很理想的。它还可以迁移到 React Native,直接编写简单的客户端应用。
-
Angular;
是 Google 推出的 Web 框架,它是比较标准的 MVVM 模式。Angular 曾经因为大版本兼容性而饱受诟病,目前它的核心竞争力是与 TypeScript 结合得较好。
-
自研。
2. 兼容性和适配性
前端开发的特有问题就是兼容性,到了移动时代,需要面对不同的机型,我们又需要解决适配性问题。兼容性问题到 2011 年左右都是前端的主旋律,但是在之后,随着现代浏览器的逐渐普及,兼容性问题逐渐减小,所以我们这里就不多谈兼容性问题了。
适配问题主要适配的是屏幕的三个要素:
- 单位英寸像素数(Pixel Per Inch,PPI):现实世界的一英寸内像素数,决定了屏幕的显示质量
- 设备像素比率(Device Pixel Ratio,DPR):物理像素与逻辑像素(px)的对应关系
- 分辨率(Resolution):屏幕区域的宽高所占像素数
3. 单页应用
单页应用是把多个页面的内容实现在同一个实际页面内的技术,因为失去了页面的天然解耦,所以就要解决耦合问题。也就是说,我们要在一个“物理页面”内,通过架构设计来实现若干个“逻辑页面”。
逻辑页面应该做到独立开发和独立发布,一种思路是,每个逻辑页面一个 JS,用一个 SPA 框架加载 JS 文件。
从交互的角度,这并不困难,但是,这里还有一个隐性需求:保持前进后退历史。
一般来说,前进后退历史使用 URL 的 Hash 部分来控制,但是 onhashchange 事件并没有提供前进或者后退信息,目前还没有完美的解决方案,只能牺牲一部分体验。实现单页应用的逻辑页面发布需要改造发布系统,在工程上,这也是一个比较大的挑战。