专注分享 TS、Vue3、前端架构和源码解析等技术干货。 |
|
英文悦读 · 为什么要说who ... · 1 周前 |
|
英文悦读 · 听力应该怎么练才有效? · 3 天前 |
|
BetterRead · 应对AI挑战最简单的方法 · 2 天前 |
|
清晨朗读会 · 渊源直播 · 5 天前 |
|
清晨朗读会 · 清晨朗读3180:When Your ... · 5 天前 |
前言
一、什么是代码可维护度
二、代码可维护度的重要性
三、代码可维护度的主要度量指标
3.1 变量命名的规范
3.2 注释密度及长度
3.3 代码容量
3.4 代码逻辑行数
3.5 代码圈复杂度
3.6 代码相似度
3.7 代码冗余度
3.8 代码模块依赖
四、如何提高代码可维护度
4.1 变量命名方面
4.2 注释方面
4.3 代码圈复杂度方面
4.4 代码容量方面
4.5 代码行数方面
4.6 代码相似度方面
4.7 代码冗余方面
4.8 代码模块依赖方面
五、结合现象分析代码可维护性
5.1 代码现象方面(以 react 为例)
5.2 文档现象方面
六、常用的代码质量分析工具库
6.1 typhonjs-escomplex
6.2 JSHint
6.3 ESLint
6.4 SonarQube
6.5 jscpd
七、在转转中的应用简介
八、总结
前
言
现代前端开发中,随着技术的不断更新和业务复杂度提升,代码质量逐渐成为我们关注的焦点。一个好的前端项目不仅要满足当前的业务需求,还得容易维护,这样才能快速适应未来的变化。然而在实际开发中经常会遇到各种
代码结构混乱
、
命名不规范
、
缺乏注释
、
业务逻辑复杂
、
需求变更频繁等问题
,这不仅增加了代码的维护成本,还容易引发潜在的 bug,影响项目的稳定性和开发效率。
本篇文章我们将从
多维度探讨影响前端代码可维护性的关键因素
,并
结合不同场景分享一些实用技巧与解决方案
,不管你是刚入行的新手还是经验丰富的老手,希望这些内容都能给你带来启发和帮助。
一、 什么是代码可维护度
代码可维护度指的是 代码在其运行生命周期内被修改、扩展和修复的难易程度 。它同时也是一个重要的工程质量指标, 直接影响到项目的可持续性和长期可维护性。
二、 代码可维护度的重要性
高可维护度的代码易于理解、修改和扩展,有助于减少修改代码时引入新缺陷的风险,提高代码质量和系统的稳定性。同时,它还能提高团队协作的效率,使新成员能够更快地梳理项目代码逻辑,从而降低维护成本,延长系统的生命周期。
以拼图为例,拼图制造师在设计每一块拼图时,都要仔细考虑如何分割图案,使得每一块拼图不仅容易辨认,而且能够让拼图者轻松找到其对应的位置。类似地,我们可以把代码比作一块拼图,开发人员在编写代码时同样需要精心设计,使每个模块和函数都具有明确的逻辑功能和清晰的界限。这样,当我们需要修改或扩展代码时,就能像拼拼图一样快速定位和处理,确保整个系统的稳定性和可维护性。
代码可维护度的重要性在于: 能直接影响项目的长远稳定性、可扩展性和开发效率。
三、 代码可维护度的主要度量指标
变量命名指在编写代码时,为变量选择一个描述性和有意义的名称的过程。
一个好的变量命名应当反映变量的用途和内容,使代码更加易读和易维护。
变量命名不规范的情况:
工具名称
ESLint [1]
算法分析
变量命名应当具有 明确性 , 简洁性 , 一致性 ,如:
指代码中注释行与代码行的比例,反映了代码中包含的注释信息的丰富程度。
计算公式:
指单个注释的字数或字符数,反映了一个注释在解释特定代码片段或功能时的详细程度和深度。
对于注释量的多少并没有一个固定的标准,结合论坛、中外文献以及其余大厂的经验,总结出以下相对合理的范围:
普遍在 20% 以及 30% 这两个区间
工具名称
get-comment-message [6]
算法分析
计算注释密度的步骤分析:
@babel/parser
对文件内容解析成 ast 结构
@babel/traverse
遍历对应的 ast 树,根据 enter 的 visitor 监视器去寻找
path.container.comments
节点内容
'\n'
进行分割,过滤掉空白行和只有
'\*'
的注释
parse5
的
parser
方法对文件内容解析成 ast 结构
'\n'
判断注释类型
'\n'
进行拆分
postcss
转为 css 格式(配置
postcssNested
和
postcssComment
的相关插件进行转换):
postcss([postcssNested]).process(cssCode, { parser: postcssComment }).css
postcss.parse
方法将样式代码解析成 ast 结构
vue-eslint-parser
的
parse
方法将文件内容解析成 ast 结构
'\n'
来判断注释的类型(单行/块级)
根据公式计算注释密度:
为了能较为准确的获取代码的有效注释密度,设定了一个对应的
文件行数阈值
以根据不同情况设定计算的基数。
文件代码行数
小于
某一文件行数阈值时:
自定义相关代码语句类型节点对应逻辑行数的映射对象
通过使用
@babel/traverse
遍历目标文件代码的 ast 语法树中每一个语句类型节点以及相关的特殊情况,不断叠加对应的逻辑行数
最终计算得到目标代码的总逻辑行数
vue-eslint-parser
遍历目标 template 代码的 ast 语法树中每一个语句类型节点,不断叠加对应的逻辑行数
b. 文件代码行数
大于
某一文件行数阈值时:
特殊情况:
在最终计算的总行数基数中,还要考虑代码和注释同一行的情况:
找出目标代码中所有代码节点的对应行数值,并与注释的行数值相匹配。
若存在相同的值,则将最终计算的总行数基数(总逻辑行数或有效代码总行数)加 1。
扩展:
增加针对于 函数粒度 的注释密度检测
前因:
以单个文件作为最小分析粒度难以有效指导工程师编写注释时把控其合理分布,因此将分析粒度细分成单个函数级别。
实现:
函数类型识别:(基于 ast 识别提取)
计算注释长度的步骤分析
/^[\u4e00-\u9fa51-9]+$/
进行匹配,随后直接计算字符串长度
/^[a-zA-Z\s]+$/
进行匹配,直接计算单词词长长度(根据空格分割单词)
注释密度相关:
注释长度相关:
注释密度:
if / switch / for
等相关的条件/循环等逻辑语句,要根据情况编写相应的注释说明
注释长度:
代码容量(Halstead Volume)是 Halstead 度量法中的一个指标,用于度量程序的整体规模质量和复杂度。
它是基于代码中的操作数和操作符来计算,操作数可以是变量、 常量、 函数等;操作符可用于指定对操作数执行的操作类型,大致的 JS 操作符如下:
参考各类文献、论坛后得出,代码容量一般较为合理的区间范围是在 100 - 8000 之间
工具名称
typhonjs-escomplex [18]
算法分析
针对于文件代码行数的类别去计算标准:
逻辑行数指代码中实际执行的逻辑语句行数,而不考虑代码的格式或物理行数。
其一般具备以下特征:
针对于逻辑行数,其一般占总代码行数的 30% - 70%
工具名称
getCodeLogicLine [22]
算法分析
基于 ast 结构递归监听对应的代码类型节点,根据不同类型的映射逻辑行数值以及相关特殊情况进行不断叠加。
(参考了 typhonjs-escomplex 工具的实现)
一般来说,逻辑行数在代码总行数中占比在
30% - 70%
之间(计算时取中间比例为
50%
)是比较常见的。
针对于文件和方法函数两个维度分别得出对应的最佳标准:
圈复杂度是一种用于量化一个程序的逻辑复杂度的度量,表示为代码中独立路径的数量。这有助于了解代码的复杂程度以及测试的难度。
一般最佳值在 10 左右的范围内
工具名称
typhonjs-escomplex [27]
算法分析
基于 ast 结构递归监听对应的代码类型节点,根据不同类型的映射圈复杂度值进行不断叠加
存在圈复杂度的代码类型节点:
结合分析,正常情况在 10 的范围内较为合适,上限为 15 左右
代码相似度是一种用于衡量两段代码在功能、结构或语法上的相似程度的度量。
相似代码一般具备以下特征:
1. 文本比较法
2. 词法分析法
定义:将代码解析成标记(tokens),然后比较这些标记序列的相似度。这种方法适用于检测变量名不同但结构相同的代码片段。
中间表现形式: Tokens 序列
指标:
Jaccard 相似系数
余弦相似度
3. 语法分析法
4. 语义分析法
特征提取:
克隆验证:
相似度计算:
babylon
转为 ast
使用 jscpd 的规范指标作为最佳标准较优:
代码冗余度指在代码中存在多余、重复或不必要的部分,这些部分不会影响程序的功能,但会增加代码的复杂性、维护成本和错误的可能性,甚至还会增加程序的运行时间和空间复杂度。
冗余代码一般具有以下几种表现形式:
针对于两种不同表现形式分别进行工具的分析:
1. 重复代码
2. 未被使用的文件代码
工具名称: deadcode [48]
算法分析:
glob
工具方法匹配获取到项目中所有的通用扩展类型(js, jsx, ts, tsx, ......)文件,放入数组(includedFiles)中
代码模块依赖指的是一个代码模块在运行、编译、或加载时所需的其他模块或资源。
具体来说,当一个模块需要使用另一个模块中的功能(如函数、类、变量等)才能正常工作时,这个模块就依赖于另一个模块。这种依赖关系可以是显式的(例如通过导入或引用)或隐式的(例如通过配置或依赖注入)。
模块化设计原则
模块化依赖的强弱性
分别从耦合度和内聚性进行工具的分析:
1. 耦合度
precinct
工具库,根据不同的文件模块类型使用不同的模块依赖提取方法:
require
语句)
Literal
或
StringLiteral
),直接返回该值作为依赖值
TemplateLiteral
),提取模板字符串中的原始值作为依赖值
require.main.require
调用,这种调用在 Node.js 中用于从主模块导入依赖)
@value color from './colors.css'
):提取其中的导入文件路径作为依赖值
@import
语句,直接提取目标导入路径
(/@(?:import|require)\s[''](.*)[''](?:.styl)?/g)
匹配文件内容,找出相关的导入节点语句
type
语句),直接提取目标导入路径
2. 内聚性
工具名称: lcom4go [68]
算法分析:
相关指标介绍:
LCOM4:
1. 耦合度
模块间的耦合应遵循两个原则
模块耦合的依赖方向
耦合类型的最优选择
CBO参考值(模块耦合度)
2. 内聚性
尽可能遵循单一职责原则:
每个模块应该只负责一个逻辑上的职责或功能
保持模块的低耦合性:
高内聚性通常伴随着低耦合性。模块间的耦合性越低,它们之间的依赖就越少,从而各自独立性越强
内聚性类型的最佳选择:
顺序内聚
指在一个模块内的多个组件之间存在 “一个组件的输出是下一个组件的输入” 这种 “流水线” 的关系
通信内聚
指一个模块内的几个组件要操作同一个数据;即使它们的功能不同,但它们共同处理相同的输入数据或状态
过程内聚
四、 如何提高代码可维护度
编写注释内容时一般需要遵循以下标准:
一致性
:代码注释的内容和其对应代码的真实运行逻辑是否一致
存在性
:必要代码注释是否缺失,尤其是对于复杂的逻辑或算法
可读性
:在阅读代码注释时是否存在困难
重要性
:代码注释内容是否包含了重要的额外信息
全面性
:代码注释内容是否包含了对应代码的全部重要信息
时效性
:代码注释针对于当前代码说明的有效时间
关联性
:代码注释与其附近的代码是否是紧密相关的
客观性
:注释信息无偏见、中立的程度, 不过分掺杂注释者的主观意见
查证性
:注释提供的引文等信息可查考、可验证的程度
一个文件的注释密度尽量控制在 20% - 30%
每一条注释的长度尽量遵循以下标准:
存在多种有着重复圈复杂度的逻辑方法(相似的处理方法)
优化手段:根据实际情况抽离出对应的一个方法
存在多个相同处理逻辑的且判断条件类似的分支
优化手段:抽离成一个数组循环进行处理
存在多个不同处理逻辑的且判断条件类似的分支
优化手段:抽离成一个映射对象
存在类似于 xxx && xxx.length 的分支判断条件
优化手段:考虑使用可选链形式
xxx?.length
存在重复或相似的元素渲染逻辑
优化手段:抽离出对应的元素渲染方法
useEffect 中存在散乱且冗余的逻辑
优化手段:抽离出每一个对应的处理方法逻辑
渲染元素中存在一些过长且复杂的处理逻辑
优化手段:把复杂逻辑分布简化,抽离成对应的单独渲染方法
存在多个相同类型的"或"判断条件
优化手段:可考虑使用
['xx', 'xxx'].includes(x)
的形式
存在条件判断中的值已去空,但还在条件里的逻辑方法体对指定的值进行无意义的兼容处理
优化手段:删除对应值的兼容处理
表达式的简化:
逻辑运算的简化:
使用内置函数代替手动计算:
合并赋值操作:
使用三元运算符简化条件语句:
合并字符串连接(优先选用模板字符串):
优化对象属性的赋值:
简化判断时的布尔表达式:
提取函数的公共方法:
合理使用数组循环来处理相似逻辑:
合理利用函数来处理逻辑:
合并条件来处理相似逻辑:
根据实际情况利用对象字面量来替代多重条件分支结构:
定期检查和删除未使用的变量、函数和代码片段。
多个地方出现相似的功能逻辑,但通常只是变量或操作略有不同
优化手段:使用函数封装重复逻辑,增加代码重用性,从而减少重复代码
页面中存在多个结构相似的组件或模块,只是由于实现时使用了不同的代码,导致代码重复
优化手段:将共享的功能模块化,通过传递不同的参数来实现不同的功能,从而减少重复代码
多次使用相同的循环或映射操作来处理不同的数据集
优化手段:通过使用高阶函数和函数式编程的思想,可以将这些操作进行抽象,从而减少重复代码
当根据条件来拼接字符串时,可能会导致代码中出现多个相似的字符串拼接操作
优化手段:使用模板字符串和对象解构来简化字符串的拼接过程,从而减少重复代码
不同函数中有重复的逻辑
优化手段:提取公共逻辑到一个单独的函数中
相似数据的批量重复操作
优化手段:使用循环代替重复的操作
处理多个基于相同数据类型条件判断
优化手段:使用对象或映射表
需要构建包含变量的字符串
优化手段:使用模板字符串
删除未被引用的代码文件以及代码语句
业务逻辑与 UI 逻辑的混杂
优化手段:将核心逻辑和 UI 逻辑分离到不同的模块
存在双向依赖的模块
优化手段:通过引入第三方模块或事件系统来打破这种循环依赖
直接引入依赖并在字面意义上直接写进逻辑里
优化手段:使用依赖注入
多个模块相互依赖,依赖关系变得复杂化
优化手段:设计模块依赖的树形结构,确保依赖关系是单向的,并且每个模块只依赖于其直接父模块
各个模块之间可能需要进行大量通信,导致高度耦合
优化手段:使用事件驱动架构进行通信,减少模块直接依赖
codeIf
)
useEffect
的使用场景,注意依赖项的设置以防重复执行
useEffect
中涉及复杂逻辑的计算,可尽量使用一些相关的 hook 函数处理(如
useMemo
)
useState
存储过多或过少的数据信息:存储了过多的信息,可能会导致状态变得臃肿和难以管理。若存储的信息过少,可能会导致需要在多个组件之间传递额外的
props
来共享信息
useState
的状态对象或数组,否则不会触发重新渲染
useState
从而导致的性能问题:配合防抖等手段减少调用
useState
的频率或将状态提升到父组件中集中管理
useState
导致不必要的状态更新:将多个
useState
合并为一个对象,减少状态更新次数,使更新更加集中
context
或一些状态管理库(
Redux
、
MobX
等)做嵌套组件数据的统一管理
props
参数,将多个关联性强的
props
合并为一个对象,或者将其归类封装成数据结构
针对以上这种代码规范难以维护的现象,可以在团队中制定出一个适当的代码开发和使用规范,并且在模块上线前进行一个集体的 CR
针对以上这种文档缺失难以维护的现象,可以在团队定期开展查缺补漏的工作。通过建立标准化的文档流程,明确文档责任人,定期检查业务、核心逻辑、组件及变更记录等文档的完整性和时效性,确保文档随代码同步更新。
typhonjs-escomplex 是一个强大的 JavaScript 代码复杂度分析工具,旨在帮助开发者深入了解和优化代码结构。通过对代码的静态分析,它可以生成详细的复杂度报告,涵盖圈复杂度,Halstead 复杂度度量以及可维护性指数等关键指标。
github 地址:
https://github.com/typhonjs-node-escomplex/typhonjs-escomplex
JSHint 是 JavaScript 代码的静态分析工具,用于检查 JavaScript 代码中的问题和风格违规。虽然 ESLint 在功能上更为强大,但 JSHint 仍然被某些项目使用。
github 地址:
https://github.com/jshint/jshint
|
英文悦读 · 为什么要说who cares,而不能说who care? 1 周前 |
|
英文悦读 · 听力应该怎么练才有效? 3 天前 |
|
BetterRead · 应对AI挑战最简单的方法 2 天前 |
|
清晨朗读会 · 渊源直播 5 天前 |
|
清晨朗读会 · 清晨朗读3180:When Your Mind Can't Let Go (3) 5 天前 |
|
V保险 · 想让熟人找你买保险?这一招就很好用! 8 年前 |
|
吃喝玩乐在北京 · 一键体验总统级座驾,享受免费送机服务 8 年前 |
|
医药魔方 · 医药魔方招聘:数据分析师、高级项目经理、编辑/记者 7 年前 |
|
全球见证分享网 · 恩典365 20170719| 恩典:奋力抓住有价值的事物 7 年前 |
|
揭幕者 · 8月28日龙虎榜揭秘:游资机构共同出货一股 7 年前 |