一、前言
控制修改引入问题一直是大部分公司产品迭代/项目研发过程中的痛点问题。修改引入问题的控制点寻找也是作为前端开发应该思考的问题。在拿到这个问题后,进行了简单的思考。
二、分析
当前开发问题拦截流程
可以发现,如果开发对修改场景不熟悉,自测试又不够充分,能够拦截引入问题的流程,只有代码审核人、门禁和DT自动化测试;
- 代码审核人:我天天要合几十个MR,又没法实时测试,光看代码让我看出引入问题,啥事都不干了?
- 门禁:我很傻,只能检查检查编译问题。
- 自动化测试:我很牛,我能覆盖所有引入问题。但是你要花一年时间给我开发所有用例,覆盖一个产品/项目的所有场景。
这就是我们项目现状。我们属于前端项目团队,DT界面自动化测试刚起步,不可能覆盖所有场景;前端代码每次提交量大,光看代码不调试很难看出问题;面临问题流入后端多,改一个问题引入两个问题的痛点;
添加一个新流程
在分析上述原始流程后,觉得问题出在对于引入问题的检测,局限在人工检视层面,缺少自动化扫描工具;
是否可以通过一种方式,能够自动检查修改是否会引入问题?这里我想到了场景管理关系管理:
即场景库管理。
如图,开发通过确定自己修改的场景去场景库中寻找与场景关联的关联场景,然后再确认这些关联场景有没有覆盖到,将检查结果附在问题单走单环境,作为走到测试前的一道新拦截保障;
三、确定方案
按照上述想法,我决定带团队做一个简单的场景库管理系统;由我负责使用nodejs
编写后台及数据库相关的代码,提供场景库管理API;由同事负责前端人机交互页面的开发;
可想而知,整个流程修改需要完成需要3个步骤;
- 1、场景库管理系统开发(提供场景增、删、改、查及维护场景间关联关系)
- 2、场景库输入
- 3、输入场景关联关系
- 4、开发访问系统获取场景关联关系,截图确认
程序员出生,急于做技术的我,立即动手开干,3天时间搞完了第一步场景库管理系统的开发;然后到场景库输入和维护场景关联关系的时候,我犯了难:
- 1、场景很多,我按照什么维度梳理?
- 2、每种场景关联关系复杂,关联关系巨大,谁有能力去维护?
- 3、开发修改的代码,怎么知道影响了哪个场景?
- 4、…
当我对着只有零星几条测试场景的场景库管理系统的时候,树状结构的场景库引起了我的灵感:
修改的东西是什么?是代码文件!代码文件就是树状的结构!如果我通过修改的代码文件,就能推断出可能受到影响的文件,岂不是回归了从代码修改到代码影响确认的端到端验证的本质?
而且,代码修改的文件,本身就在提交MR流程中可以获取,如果能实现自动化分析,那将极大提高影响的分析的精准性和效率;现在问题就一个,如何分析代码文件的依赖关系?
对于前端来说
这对于写前端的同事而言怕是不是什么大问题了,了解 AST
和bable
的同事们自然清楚,通过 @babel/parser
提供的工具,可以分析代码语法树,从而得知代码依赖关系,再从依赖关系映射到具体物理文件:
对于非前端语言来说
只要将获取代码依赖关系的逻辑单独抽为Interface
,约定返回的数据格式,由不同的语言分析工具实现即可。
四、确定技术路线
4.1 关键技术点
数据结构
文件依赖关系是图的关系,不是树的关系;因为会存在以下两种情况,树无法表达,或者在使用dfx
遍历的时候会出现死循环;
循环依赖
故要先创建节点,再创建关联关系:
const graph = {
nodes: [
{ id: 0, name: 'foo.ts', children: [1] },
{ id: 1, name: 'bar.ts', children: [2] },
{ id: 2, name: 'baz.ts', children: [0] }
]
}
由于我采用nodejs
编写,javascript
没有Graph
的标准库,故只能从github
上寻找非标准库,并进行二次封装;这里我使用的是 dsa.js
依赖分析
针对前端js
语言,这里用的是bable-parser
。关键示例代码:
const fs=require("fs");
const path=require("path");
const babel=require("@babel/core");
const parser=require("@babel/parser");
const traverse=require("@babel/traverse").default;// 默认es module导出
const moduleAnalyser=(filename)=>{
const content=fs.readFileSync(filename,"utf-8");// 读取文件内容
const ast=parser.parse(content,{
sourceType:"module"
})
const dependencies={};
traverse(ast,{
ImportDeclaration({node}){
const dirname=path.dirname(filename);//filename对应的文件夹路径
const newFile="./"+path.join(dirname,node.source.value);
dependencies[node.source.value]=newFile;
}
})
const { code } = babel.transformFromAst(ast,null,{
presets:["@babel/preset-env"]
})//转换ast
return {
filename,
dependencies,
code
}
}
const moduleInfo=moduleAnalyser("./src/index.js");// 入口函数
console.log(moduleInfo);
持久化
elasticsearch
,永远的神。不多说,前端数据库首选;
4.2 设计架构
有了技术点还不够,为了让更多的人使用,我的目标是做成sdk,让使用的人通过少量的接口/模块实现就可以达成从依赖分析、关联关系分析、持久化、关系查询一体化的目的;另外、图的关系可以是有向图(依赖关系)和无向图(关联关系)为此,设计应该是灵活的,应用场景上应该可以应用在关联关系分析(上述场景关联系统)或者依赖关联分析(本文代码白盒依赖分析)上;语言分析上,java、python语言等依赖分析成为可能:
只需要通过约定通用的GraphData
存储格式,将关联图的操作SDK
封装在DRManager
中,即可实现。
例如代码分析模块:
图的顶点属性也支持自定义,只需要继承DRNode
即可。例如我的应用场景 代码白盒依赖分析,就可以用DRNode
自定义拓展属性记录顶点(文件)的父子关系,从而生成文件的目录树;
五、成果展示
数据结构、逻辑处理都在sdk
和server
,页面做的比较粗糙:大家看懂就行:
SDK的开源工作会在后续进行,到时候会贴github地址;欢迎关注
六、结语
发散来说,掌握了代码的白盒依赖关系,可以延伸的事情很多。依赖关系是源代码的另一种表达方式,也是把控巨型项目质量极为有利的工具。我们可以利用依赖关系挖掘出无数的想象空间,比如无用文件查找、版本间变动测试范围精确化等场景。若结合 Android、iOS、C++ 等底层依赖关系,就可以计算出更多的分析结果。后续这块可以作为团队软件工程能力提升的一个业务突破点;
参考:
https://zhuanlan.zhihu.com/p/94284179
https://segmentfault.com/a/1190000021363222