代码覆盖率在性能优化上的一种可行应用
You can't manage what you can't measure.
一件事如果你无法衡量它,你就无法管理它。——管理大师 彼得·德鲁克
前言
JavaScript 是前端应用主要语言,相较于其他平台编程语言,JS资源多数情况下要通过网络进行加载,那么代码的体积直接影响了页面加载执行时间。“无效的代码”的多寡直接影响到了我们的代码质量,所以度量代码的执行覆盖率是一项重要的优化前置工作。
什么是代码覆盖率
▐ Dead code
Dead code 也叫无用代码,这个概念应是在编译时静态分析出的对执行无影响的代码,举个例子:
// a.js
const a = 1;
const b = 2; /* dead code */
export default a;
// index.js
import a from './a.js';
export default function() {console.log(a);
}
通常我们用 Tree Shaking 在编译时移除这些 dead code以减小代码体积。
▐ 冗余代码
而代码覆盖率里所提到的冗余代码 和 Dead Code 又略有不同,简单来说Dead code适用于编译时,而 Code coverage 适用于运行时。
Dead code 是任何情况下都不会执行的代码,所以可以再编译阶段将其剔除。
冗余代码 是某些特定的业务逻辑之下并不会执行到这些代码逻辑(比如:在首屏加载时,某个前端组件完全不会加载,那么对于“首屏”这个业务逻辑用例来讲,该前端代码就是冗余的)
▐ 代码覆盖率
代码覆盖率(Code coverage)是软件测试中的一种度量指标。即描述测试过程中(运行时)被执行的源代码占全部源代码的比例。
怎么度量代码覆盖率
▐ Chrome 浏览器 Dev Tools
chrome 浏览器的 DevTools 给我们提供了度量页面代码(JS、CSS)覆盖率的工具 Coverage。
使用方式:Dev tools —— More tools —— Coverage
可度量代码类型:JS CSS
统计可视化形式:
使用率是以byte字节来计算的;
当我们选择一段脚本资源即可在 Source 栏可以看到加载页面时当前资源 run过得代码(蓝色)和没有run过得代码(红色);
缺点:显然,目前大部分网页上的JS脚本基本都是经过混淆压缩打包过后的产物,对于开发者而言,这种覆盖率可读性及参考价值不大。
TIPS:当然,如果在拥有 source map 的情况下也是可以用浏览器查看源代码的覆盖率的:
1.在 source tab 中找到当前页面的 js 资源文件(当然已经被混淆的面目全非)
2.输入 sourcemap URL(以 def 发布平台为例,在构建结果中可找到)
3. 在 webpack:// 目录下即可查看对应源码的大致覆盖率(不过没有什么消费价值)
那么问题来了,有没有一种方法可以令开发者了解 源代码 的代码覆盖率的值呢?
▐ Istanbul(NYC)
这个软件以土耳其最大城市伊斯坦布尔命名,因为土耳其地毯世界闻名,而地毯则是用来覆盖的。
Istanbul或者 NYC(New York City,基于 istanbul 实现) 是度量 JavaScript 程序的代码覆盖率工具,目前绝大多数的node代码测试框架使用该工具来获得测试报告,其有四个测量维度:
line coverage(行覆盖率-每一行是否都执行了) 【一般我们关注这个信息】function coverage(函数覆盖率-每个函数是否都调用了)branch coverage(分支覆盖率-是否每个 if 代码块都执行了)statement coverage(语句覆盖率-是否每个语句都执行了)
可以度量的代码类型:JS TS
统计可视化的形式:
HTML
terminal
缺点:目前使用 istanbul 度量网页前端JS代码覆盖率没有非侵入的方案,采用的是在编译构建时修改构建结果的方式埋入统计代码,再在运行时进行统计展示。
我们可以使用 babel-plugin-istanbul 插件在对源代码在 AST 级别进行包装重写,这种编译方式也叫 代码插桩 / 插桩构建(instrument)
▐ 插桩构建
我们如果要度量这一段代码哪些代码执行了 哪些代码没有执行,我们会怎么做呢?
// add.js
function add(a, b) {return a + b
}
module.exports = { add }
我们可以很容易的想到加一些“装饰性”的代码在我们的源码里面,那么当代码一行一行的执行到某处时,那么我们就在全局环境变量中记录一下:
// 全局对象记录了 __coverage__ 记录了上面代码中的语句和函数的执行次数
const c = (window.__coverage__ = {// "f" 表示每一个 function 被执行的次数// 当前代码只有一个 function 因此,f 数组只有一个 且记录值为 0f: [0],// "s" 表示每一个 statement 被执行的次数// 3 个 statement 全部都以 0 赋值s: [0, 0, 0],})// 函数定义是一个语句(statement),那么我们 +1
c.s[0]++function add(a, b) {// 如果 add 函数(function)被调用,f +1,且改调用语句 s +1c.f[0]++c.s[1]++return a + b}
// add 被调出语句 s +1
c.s[2]++
module.exports = { add }
istabul 确实也是这么做的,babel-plugin-istanbul 在构建过程中分析 AST 并将相应统计单元(语句、函数、分支等)做装饰代码的添加,最终在代码运行之后,输出一份 json 格式的数据:
{"/Users/bairuobing/test/istanbul.js":{"path":"/Users/bairuobing/test/istanbul.js","s":{"1":1,"2":0,"3":1},"b":{},"f":{"1":0},"fnMap":{ // function 的开始结束位置信息"1":{"name":"add","line":1,"loc":{"start":{"line":1,"column":0},"end":{"line":1,"column":19}}}},"statementMap":{ // statement 的开始结束位置信息"1":{"start":{"line":1,"column":0},"end":{"line":3,"column":1}},"2":{"start":{"line":2,"column":4},"end":{"line":2,"column":16}},"3":{"start":{"line":4,"column":0},"end":{"line":4,"column":24}}},"branchMap":{ // branch 的开始结束位置信息}}
}
当我们在运行代码过后,得到了上面的 json 便可以消费它了。
# terminal 形式输出
nyc report --reporter=text
# HTML 形式输出
nyc report --reporter=lcov --exclude-after-remap=false
terminal
HTML
线程池概述代码覆盖率在iHome Rax开发套件 Tbox 中的应用
tips:tbox 每平每屋 消费者端 本地开发套件
既然我们知道了源代码的代码覆盖率,我们可以用它为性能优化做些什么贡献呢?
当工程主 bundle 较大,那么采用拆包较大的/无用的前端组件来瘦身首屏主 JS 包不失为一种可行的选择,此时就可以根据代码覆盖率来决定优化哪些代码。
▐ 代码分割
React.lazy 已经为我们提供了一种不错的思路,就是利用动态加载模块规范 import() (webpack对import()解析为代码分割)的能力来实现前端组件代码懒加载/动态加载。
以此为灵感,那么为何不将某些组件通过动态引入的方式加载,来以此换取首页 bundle 的瘦身呢?
// 动态引入组件
// ThisIsBigMod
import { createElement, useState, useEffect } from 'rax';
export default (props) => {const [AsyncMod, setAsyncMod] = useState(null);useEffect(() => {const load = async () => {const Module = await import('./ThisIsBigMod'); // 关键try {setAsyncMod(Module);} catch (e) {console.log(e);}};load();}, []);if (!AsyncMod || !AsyncMod.default) {return null;}return <AsyncMod.default {...props} />;
};
▐ 下一步
我们能通过代码覆盖率统计出哪些组件的代码首屏使用率为0(或者门槛值30%以下),并在项目工程中自动生成一个持久化的文件配置(app.json中),之后依据配置将这些低使用率的组件代码在生产构建时将产物代码改写为动态引入。
于是有了以下方案:
▐ 如何使用
1.该功能需要项目下安装以下 build 插件(如 tbox 新建的项目已安装以下插件可忽略):
@ali/build-plugin-coverage
@ali/build-plugin-async-components
tnpm install --save-dev @ali/build-plugin-coverage @ali/build-plugin-async-components
2. build.json
// build.json
"plugins": [......"@ali/build-plugin-coverage",["@ali/build-plugin-async-components",{"active": true}]
]
运行 Tbox:
3. 插桩构建
依赖 @ali/build-plugin-coverage
通过插桩将源码中插入统计代码
本地构建之后页面全局会注入__coverage__变量(可在页面控制台输出该变量检查插桩是否成功)
4. 分析自动化生成配置
等待完成首屏渲染(或者完成自定义的一系列行为用例),此刻插桩代码已经完成了代码使用率的统计
打开 Tlog 小工具 点击代码优化->生成源代码优化配置,此刻 Tbox 本地服务已经接收到了发来的__coverage__并完成后续的代码覆盖率分析,通过分析使用率低于门槛值的组件文件,将这些组件的项目相对路径写入 app.json 的 modsPath 字段下
此刻 @ali/build-plugin-async-components 会根据 modsPath 配置自动将组件构建为动态引入的方式
如果您想通过自己的配置来完成组件异步化,请直接手动修改 app.json 里的 modsPath 字段,只需依赖 @ali/build-plugin-async-components 插件再次构件即可
此时我们条件加载被异步化的组件会发现,BigMod 组件已经被动态的拆包引入了,页面主 js 包也得到了瘦身,搞定!
写在最后
istanbul 在 node 环境下跑测试用例代码能度量覆盖率是由于其对运行时模块加载器的源代码拦截,但是比较遗憾的是,本文介绍的代码插桩分析覆盖率这会引入一些多余的桩代码,或许采用 puppeteer 无头浏览器提供的Coverage api + sourceMap 逆编译的思路来进行度量是一种更加完美的方式,期待与诸君一起探索,继续努力!
团队介绍
我们是大淘宝-家装家居技术-前端团队,团队支撑大淘宝家居家装业务。旗下包括:每平每屋App、淘宝【极有家】频道。我们连通电商平台和商家店铺,覆盖居家生活、装修设计、线下市场,3D场景化展现居家生活,我们力求让每件单品都不再孤立呈现,置身其中,感受家的优选方案。期待与您一起共筑美好的理想家。
✿ 拓展阅读
作者|白若冰(若冰)
编辑|橙子君
代码覆盖率在性能优化上的一种可行应用相关推荐
- 总结:Hive性能优化上的一些总结
Hive性能优化上的一些总结 注意,本文百分之九十来源于此文:Hive性能优化,很感谢作者的细心整理,其中有些部分我做了补充和追加,要是有什么写的不对的地方,请留言赐教,谢谢 前言 今天电话面试突然被 ...
- 多层科目任意组合汇总报表的性能优化 (上)
一 问题背景 我们先来看一张资产负债表: 这是一个典型的中国式复杂报表格式,其复杂并不在于布局,而在于其中"期末余额"的每个单元格都是一个需要独立计算的指标,互相之间几乎没有关系, ...
- h5如何上传文件二进制流_Hadoop如何将TB级大文件的上传性能优化上百倍?
这篇文章,我们来看看,Hadoop的HDFS分布式文件系统的文件上传的性能优化. 首先,我们还是通过一张图来回顾一下文件上传的大概的原理. 由上图所示,文件上传的原理,其实说出来也简单. 比如有个TB ...
- 阿里三面 Android 研发岗,竟然挂在了性能优化上……
作为一个程序员,性能优化是无法避开的事情,并且性能优化也是软件系统中最有挑战的工作之一,更是每个工程师都需要掌握的核心技能. 性能问题和Bug不同,后者的分析和解决思路更清晰,很多时候从应用日志即可直 ...
- 阿里三面 Android 研发岗,竟然挂在了性能优化上…
作者:code小生 作为一个程序员,性能优化是无法避开的事情,并且性能优化也是软件系统中最有挑战的工作之一,更是每个工程师都需要掌握的核心技能. 性能问题和Bug不同,后者的分析和解决思路更清晰,很多 ...
- 【性能优化】 之 几种常见的等待事件的演示示例
内容大纲: 1.分别用表和索引上数据的访问来产生db file scattered read等待事件,等待事件需要在v$session_wait和10046 trace文件中显示出来,贴出整个演示过程 ...
- Android性能优化案例研究(上)
为什么80%的码农都做不了架构师?>>> 英文原文:Android Performance Case Study 编译:ImportNew - 孙立 译 者前言: 这是Goo ...
- 前端性能优化的七种方法
前端性能优化主要有七种方法,包括减少请求数量.减少资源大小.优化网络连接.优化资源加载.减少重绘回流.使用性能更好的API和webpack优化 1.减少请求数量 1.1 图片处理 1.1.1 雪碧图 ...
- Elasticsearch性能优化实战指南
点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 0.背景 在当今世界,各行各业每天都有海量数据产生,为了从这些海量数据中获取想要的分析结果,需 ...
最新文章
- java heep space错误解决办法
- python 打开文件-Python打开文件的方式
- Andy's First Dictionary
- 8数据提供什么掩膜产品_博硕能为你提供什么产品?
- 诗与远方:无题(三十五)- 曾经写给妹子的一首诗
- python数据挖掘学习笔记】十九.鸢尾花数据集可视化、线性回归、决策树花样分析
- python神器pandas_Python中的神器Pandas,但是有人说Pandas慢...
- 计算机毕业设计java基于ssm的企业工资管理系统
- flask 定时任务 flask-apscheduler
- web前端(1)——了解什么是前端,以及与后端的关系
- 创业起步阶段需要注意什么?
- 【在线可测】通用中文点选验证码识别
- php网页视频播放插件下载_视频播放插件Video.js
- OC10 -- block / 多态
- WEB页面SEO —— 网站TDK优化细节
- Flex中实现double-click修改DataGrid
- 刺沙--冰雪传奇自动回收,使用元宝经验卷轴(手机按键)
- 钢的基本知识03——钢材常用规格型号
- Python学习记录 使用tensorflow 2.8 完成猫狗识别 使用keras构建CNN神经网络
- 局域网计算机无法被访问,解决局域网无法访问故障