目录
- 一、使用的相关工具
- 二、冗余代码的分类和压缩策略
- 2.1 无用代码
- 2.2 重复代码
- 2.3 相似代码
- 三、长久治理机制
- 3.1 git-hooks
一、使用的相关工具
以下工具都有各自详细说明的文档。除非有必要,下文不再对其相关使用作详细说明。
仓库代码查重工具:https://github.com/kucherenko/jscpd
文本在线diff工具:https://tool.chinaz.com/tools/diff
node轻量级git工具:https://github.com/steveukx/git-js
包与文件引用检查:https://github.com/depcheck/depcheck
二、冗余代码的分类和压缩策略
2.1 无用代码
无用代码是完全没有被引用的代码。
通过自然发现和depCheck工具,我们可以发现仓库中的无用代码。
我们可以直接删除无用代码。
注意!depCheck工具检查出的无用文件,不一定是真的没有用到。一些看似无用的文件可能会在动态路由构建或者在webpack打包过程中使用,注意甄别。
2.2 重复代码
重复代码是与存量代码内容相同的代码。
通过自然发现和jscpd工具,我们可以发现仓库中的重复代码。
我们可以提升代码的文件层级,然后修改目标引用,以此来解决重复代码。
2.3 相似代码
相似代码是与存量代码内容相似度高的代码。
通过自然发现和jscpd工具,我们可以发现仓库中的相似代码。
代码相似度高不意味着场景通用,对于相似代码需要有选择性的合并:
场景 | 措施 |
---|---|
输入输出处理 | 公共format方法 |
trycatch处理 | 全局捕获/封装请求方法 |
… | … |
三、长久治理机制
代码重复度的优化非一日之功。健康的代码重复度需要长久治理机制的形成。
3.1 git-hooks
我的目的是希望在每一次commit之前,都对变更代码进行查重(就像lint-staged一样),那么我们可以构造以下流程:
package.json配置如下:
{
...
"husky": {
"hooks": {
"pre-commit": "node ./scripts/repeat-check",
...
}
},
"jscpd": {
"threshold": 5,
"reporters": [
"html",
"console"
],
"ignore": [
".git",
...
],
"format": [
"javascript",
"typescript",
"jsx",
"tsx"
],
"absolute": true
}
}
新增scripts/repeat-check.js:
/** @description 对当前git暂存区或更改区的代码,进行查重。若查重比超过预设阈值,阻止提交 */
const simpleGit = require('simple-git')();
const jscpd = require('jscpd');
const cloneDeep = require('lodash/cloneDeep');
const config = require('../package.json');
const { detectClones } = jscpd;
const threshold = config.jscpd.threshold || 5;
/** 获取暂存区或更改文件的目录 */
const getChangedFilePaths = (gitStatus) => {
const stagedFilePaths = gitStatus.staged;
const changedFilePaths = gitStatus.files.map((file) => file.path);
return stagedFilePaths.length ? stagedFilePaths : changedFilePaths;
};
/** 获取百分比 */
const formatNumberToPercent = (num = 0) => {
return Number(num * 100).toFixed(2);
};
/** 在所有重复比对中,筛选出与更改文件相关的 */
const getChangedClones = (clones, changedFilePaths) => {
const changedFileArr = cloneDeep(changedFilePaths);
return clones.filter((clone) => {
const { duplicationA, duplicationB } = clone;
const { sourceId: absolutePathA } = duplicationA;
const { sourceId: absolutePathB } = duplicationB;
const matchPath = changedFileArr.find(
(changedPath) =>
absolutePathA.indexOf(changedPath) > -1 || absolutePathB.indexOf(changedPath) > -1
);
if (matchPath) {
changedFileArr.splice(changedFileArr.indexOf(matchPath), 1);
}
return matchPath;
});
};
/** 打印更改文件相关的重复比对 */
const printChangedClones = (changedClones) => {
console.log(`A total of ${changedClones.length} clones were found.\n`);
changedClones.forEach((changedClone) => {
const { format, duplicationA, duplicationB } = changedClone;
const { start: startA, end: endA, sourceId: sourceIdA } = duplicationA;
const { start: startB, end: endB, sourceId: sourceIdB } = duplicationB;
const { line: startLineA, column: startColumnA } = startA;
const { line: endLineA, column: endColumnA } = endA;
const { line: startLineB, column: startColumnB } = startB;
const { line: endLineB, column: endColumnB } = endB;
console.log(`Clone found (${format}):`);
console.log(` - ${sourceIdA} [${startLineA}:${startColumnA} - ${endLineA}:${endColumnA}]`);
console.log(` ${sourceIdB} [${startLineB}:${startColumnB} - ${endLineB}:${endColumnB}]\n`);
});
};
const main = async () => {
const gitStatus = await simpleGit.status();
const changedFilePaths = getChangedFilePaths(gitStatus);
const clones = await detectClones({
...config.jscpd,
absolute: true,
silent: true,
});
const changedClones = getChangedClones(clones, changedFilePaths);
const duplicatedPercent = formatNumberToPercent(changedClones.length / changedFilePaths.length);
printChangedClones(changedClones);
if (duplicatedPercent > threshold) {
console.log(
`Too many clones(${duplicatedPercent}%), far over over the threshold(${threshold}%).`
);
process.exit(1);
}
process.exit(0);
};
main();
注意!pre-commit如果已经配置了lint-staged,再加上查重校验,commit耗时可能会比较长(这也是没办法的事情,毕竟全量代码走两次抽象语法树校验耗时是少不了的)