当前位置: 首页 > 科技观察

代码覆盖率在性能优化中的一种可行应用

时间:2023-03-12 18:34:30 科技观察

作者|若冰你不能管理你不能衡量的东西。如果你不能衡量一件事,你就无法管理它。——管理大师彼得德鲁克前言JavaScript是前端应用的主要语言。与其他平台编程语言相比,JS资源大多通过网络加载,因此代码的大小直接影响页面加载执行时间。“无效代码”的多??少直接影响到我们的代码质量,因此衡量代码执行覆盖率是一项重要的预优化工作。什么是代码覆盖率1、死代码死代码也叫无用代码。这个概念应该是编译时静态分析的代码,对执行没有影响。例如://a.jsconsta=1;常量b=2;/*死代码*/exportdefaulta;//index.jssimportafrom'./a.js';exportdefaultfunction(){console.log(a);}通常我们使用TreeShaking来移除这些死代码减少代码大小。2。冗余代码代码覆盖率中提到的冗余代码与死代码略有不同。简单来说,Deadcode适用于compilation,Codecoverage适用于runtime。死代码是在任何情况下都不会执行的代码,因此可以在编译期间将其删除。冗余代码是指某些特定的业务逻辑不会执行这些代码逻辑(例如:加载首屏时,根本不会加载某个前端组件,所以对于“首屏”的业务逻辑用例,前端代码是多余的)。3。代码覆盖率代码覆盖率(Codecoverage)是软件测试的一种度量。也就是说,它描述了测试期间(运行时)执行的源代码占源代码总数的比例。如何衡量代码覆盖率1、Chrome浏览器的DevToolsChrome浏览器的DevTools为我们提供了一个工具Coverage来衡量页面代码(JS、CSS)的覆盖率。使用方法:Devtools——Moretools——Coverage可测代码类型:JSCSS统计可视化形式:使用率以字节计算;当我们选择一个脚本资源时,我们可以在Source栏中看到加载页面上,当前资源运行的代码(蓝色)和还没有运行的代码(红色);缺点:很明显,目前网页上的绝大部分JS脚本,基本上都是经过混淆、压缩、打包后的产物。对于开发者来说,这种覆盖可读性和参考价值不大。TIPS:当然,如果你有源图的话,也可以用浏览器查看源码的覆盖率:(1)在source选项卡中找到当前页面的js资源文件(当然已经混淆了面目全非)。(2)输入sourcemapURL(以def发布平台为例,可以在构建结果中找到)。(3)在webpack://目录下,可以查看对应源码的大概覆盖率(但是没有消耗值)。那么问题来了,有没有办法让开发者了解源代码的代码覆盖率的价值呢?2.Istanbul(NYC)这个软件以土耳其最大的城市伊斯坦布尔命名,因为土耳其的地毯世界闻名,地毯就是用来盖的。Istanbul或NYC(纽约市,基于istanbul实现)是一个衡量JavaScript程序代码覆盖率的工具。目前,大多数节点代码测试框架都使用该工具来获取测试报告。它有四个衡量维度:linecoverage(线路覆盖)Coverage-每行是否执行)【一般我们关注的是这个信息】functioncoverage(函数覆盖-是否调用每个函数)branchcoverage(分支覆盖-是否每一个if代码blockisexecuted)语句覆盖率(statementcoverage-每条语句是否执行)可测代码类型:JSTS统计可视化形式:HTMLterminal缺点:目前还没有非侵入式的方案可以使用istanbul,就是在编译构建时修改构建结果的方法,嵌入到统计代码中,然后在运行时显示统计信息。我们可以使用babel-plugin-istanbul插件对源代码进行AST级别的打包和重写。这种编译方式也称为代码检测/检测。3.stake构建如果我们要衡量这段代码中哪些代码被执行了,哪些代码没有被执行,我们会怎么做呢?//add.jsfunctionadd(a,b){returna+b}module.exports={add}我们很容易想到在我们的源代码中添加一些“装饰”代码,然后当代码逐行执行到某处,那么我们在全局环境变量中记录一下://全局对象记录__coverage__记录了上面代码中语句和函数的执行次数constc=(window.__coverage__={//"f"表示次数oftimeseachfunctionisexecuted//当前代码只有一个函数,所以f数组只有一个并且记录值为0f:[0],//"s"表示每条语句执行的次数//3个语句都赋0s:[0,0,0],})//函数定义是A语句(statement),那么我们+1c.s[0]++functionadd(a,b){//如果add函数(function)被调用,f+1,调用语句s+1c.f[0]++c.s[1]++returna+b}//add调用语句s+1c.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":{//语句的起止位置信息"1":{"start":{"line":1,“列”:0},“结束”:{“行”:3,“列”:1}},“2”:{“开始”:{“行”:2,“列”:4},“结束”:{“行”:2,“列”:16}},“3”:{“开始”:{“line":4,"column":0},"end":{"line":4,"column":24}}},"branchMap":{//分支起止位置信息}}}当我们运行代码后,得到上面的json后就可以消费了#terminalformoutputnycreport--reporter=text#HTMLformoutputnycreport--reporter=lcov--exclude-after-remap=falseterminalHTML代码覆盖在Application中iHomeRax开发包Tbox中的tips:tbox是一个消费者一户一户的本地开发包,既然我们知道了源码的代码覆盖率,那我们可以用它来为性能优化做些什么贡献呢?当主工程bundle比较大的时候,那么通过解包大型/无用的前端组件来瘦身首屏的主JS包是一个可行的选择,此时可以根据代码覆盖率来决定优化哪些代码。1.代码拆分React.lazy为我们提供了一个很好的思路,就是利用动态加载模块规范import()的能力(webpackparsesimport()intocodesegmentation)实现前端组件代码的懒加载/动态加载。受此启发,为什么不动态加载一些组件来换取精简主页包呢?//动态导入组件//ThisIsBigModimport{createElement,useState,useEffect}from'rax';exportdefault(props)=>{const[AsyncMod,setAsyncMod]=useState(null);useEffect(()=>{constload=async()=>{constModule=awaitimport('./ThisIsBigMod');//keytry{setAsyncMod(Module);}catch(e){console.log(e);}};加载();},[]);如果(!AsyncMod||!AsyncMod.default){返回null;}return;};2、下一步我们可以通过代码覆盖率统计哪些组件的代码首屏使用率为0(或低于30%的阈值),并在项目中持久化文件配置(在app.json中)在项目中自动生成,然后在生产建设时根据配置将这些低使用率的组件代码改写为动态导入。于是就有了以下解决方案:3、如何使用(1)该功能需要在项目下安装以下构建插件(例如,如果已经安装了tbox的新项目,则可以忽略以下插件):@ali/build-plugin-coverage@ali/build-plugin-async-componentstnpminstall--save-dev@ali/build-plugin-coverage@ali/build-plugin-async-components(2)build.json//构建.json"plugins":[..."@ali/build-plugin-coverage",["@ali/build-plugin-async-components",{"active":true}]]RunTbox:(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环境下运行测试用例代码来衡量覆盖率,这是由于其拦截了运行时模块加载器的源代码,但不幸的是,本文介绍的代码检测分析覆盖率将引入一些冗余的stub代码,或许使用puppeteerheadlessbrowser提供的Coverageapi+sourceMap反编译的思路来衡量是一个比较完美的方式。期待与您一起探索,继续努力!