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

使用 jsinspect 检测前端代码库中的重复-近似代码

时间:2023-03-12 23:02:12 科技观察

在前端代码库中使用jsinspect检测重复/近似代码经过逐步完善,我们需要考虑代码库的优化和重构,尽量写出清晰可维护的代码。好的代码往往在合理范围内尽可能避免重复代码,遵循单一职责和单一真相的原则。在这部分,我们尝试使用jsinspect自动检索代码库,根据反馈的重复或近似的代码片段进行合理的优化。当然,我们并不是简单地追求彻底剥离公共代码。过多的抽象会降低代码的可读性和可理解性。jsinspect使用babylon为JavaScript或JSX代码构建AST语法树,根据不同的AST节点类型,如BlockStatement、VariableDeclaration、ObjectExpression等标记结构相似的代码块。我们可以使用npm全局安装jsinspect命令:Usage:jsinspect[options]Detectcopy-pastedandstructurallyssimilarJavaScriptcodeExampleuse:jsinspect-I-L-t20--ignore"test"./path/to/srcOptions:-h,--帮助输出使用信息-V,--versionoutputtheversionnumber-t,--thresholdnumberofnodes(default:30)-m,--min-instancesmininstancesforamatch(default:2)-c,--configpathtoconfigfile(default:.jsinspectrc)-r,--reporter[default|json|pmd]specifythereportertouse-I,--no-identifiersdonotmatchidentifiers-L,--no-literalsdonotmatchliterals-C,--no-colordisablecolors--ignoreignorepathmatchingaregex--截断lengthtotruncatelines(default:100,off:0)我们也可以选择在项目目录下添加.jsinspect配置文件来指定jsinspect运行配置:{"threshold":30,"identifiers":true,"literals":true,"ignore":"test|spec|mock","re??porter":"json","truncate":100,}配置后,我们可以使用jsinspect-t50--ignore"test"./path/to/src分析代码库。以笔者找到的一个代码库为例,它检测到数百个重复的代码片段,典型代表如下可以看出,多次密码输入的元素在某个组件中被重复写入。我们可以选择将其封装为功能组件,将label、hintText等常用属性包裹起来,以降低代码重复率。Match-2instances./src/view/main/component/tabs/account/operation/login/forget_password.js:96,110返回{this.setState({userPwd:value})}}/>

./src/view/main/component/tabs/my/login/forget_password.js:111,125返回{this.setState({userPwd:value})}}/>
作者也对React源码进行了简要分析,在246个文件中发现了16个大概的代码片段,其中大部分重复源于目前基于Stack的reconciliation算法和之间的过渡期引起的重复基于Fiber重构的reconciliation算法,如:Match-2instances./src/renderers/dom/fiber/wrappers/ReactDOMFiberTextarea.js:134,153varvalue=props.value;if(value!=null){//Cast`value`toastringtoensurethevalueissetcorrectly.While//browserstypicallydothisasnecessary,jsdomdoesn't.varnewValue=''+value;//为了避免副作用(比如丢失文本选择),只设置valueifchangedif(newValue!==node.value){node.value=newValue;}if(props.defaultValue==null){node.defaultValue=newValue;}}if(props.defaultValue!=null){node.defaultValue=props.defaultValue;}},postMountWrapper:function(element:Element,props:Object){./src/renderers/dom/stack/client/wrappers/ReactDOMTextarea.js:129,148varvalue=props.value;if(value!=null){//Cast`value`toastringtoensurethevalueissetcorrectly.While//browserstypicallydothisasnecessary,jsdomdoes't.varnewValue=''+value;//为了避免副作用(例如丢失文本选择),仅设置valueifchangedif(newValue!==node.value){node.value=newValue;}if(props.defaultValue==null){node.defaultValue=newValue;}}if(props.defaultValue!=null){node.defaultValue=props.defaultValue;}},postMountWrapper:function(inst){作者认为在新特性的开发过程中,不一定需要一直考虑代码重构,而是应该开发相对独立的新特性***下面简单讨论一下jsinspect的工作原理,以便在项目需要的时候自定义类似的工具进行特殊的代码匹配或者抽取。jsinspect的核心工作流程可以在inspector.js文件中体现出来:...this._filePaths.forEach((filePath)=>{varsrc=fs.readFileSync(filePath,{encoding:'utf8'});this._fileContents[filePath]=src.split('\n');varsyntaxTree=parse(src,filePath);this._traversals[filePath]=nodeUtils.getDFSTraversal(syntaxTree);this._walk(syntaxTree,(nodes)=>this._insert(nodes));});this._analyze();...上面的过程还是比较清晰的,jsinspect会遍历所有有效的源码文件,提取它们的源码内容然后通过转换成AST语法树巴比伦。语法树格式如下:Node{type:'Program',start:0,end:31,loc:SourceLocation{start:Position{line:1,column:0},end:Position{line:2,column:15},filename:'./__test__/a.js'},sourceType:'script',body:[Node{type:'ExpressionStatement',start:0,end:15,loc:[Object],expression:[Object]},Node{type:'ExpressionStatement',start:16,end:31,loc:[Object],expression:[Object]}],directives:[]}{'./__test__/a.js':['console.log(a);','console.log(b);']}然后我们通过深度优先遍历算法将AST语法树上的所有节点构建一个数组,然后遍历整个数组构建要比较的对象。这里我们在运行时输入-t参数来指定要划分的原子比较对象的维度。当我们指定该参数为2时,遍历构造阶段形成的内部映射数组_map的结构如下:{'uj3VAExwF***vx0SGBDFu8beU+Lk=':[[[Object],[Object]],[[对象],[对象]]],'eMqg1hUXEFYNbKkbsd2QWECLiYU=':[[[对象],[对象]],[[对象],[对象]]],'gvSCaZfmhte6tfnpfmnTeH+eylw=':[[[对象],[Object]],[[Object],[Object]]],'eHqT9EuPomhWLlo9nwU0DWOkcXk=':[[[Object],[Object]],[[Object],[Object]]]}如果有大型代码数据,我们可能会形成许多重叠的实例。这里我们使用_omitOverlappingInstances函数去重;例如,如果一个实例包含节点abcd,另一个实例包含节点组bcde,则选择后者从数组中移除。另一种优化加速的方法是在每次比较后删除匹配的代码片:_prune(nodeArrays){for(leti=0;i