首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >项目代码清理的系统化策略:从安全验证到工程化移除

项目代码清理的系统化策略:从安全验证到工程化移除

原创
作者头像
叶一一
发布2025-08-08 18:28:53
发布2025-08-08 18:28:53
12200
代码可运行
举报
文章被收录于专栏:项目实战项目实战
运行总次数:0
代码可运行

引言

在现代前端开发中,随着业务需求不断变化和技术栈持续演进,项目中不可避免地会积累大量冗余代码

最近,我在做功能迭代的时候,发现有些代码已经不再使用,但是代码块还保留着。我再梳理功能时,产生了困惑,找到之前对应的业务负责人才了解了最新的业务。

可见,这些"代码债务"不仅增加了项目的维护成本,还会影响构建性能和新功能的开发效率。

本文将分享一套在React项目中安全、系统化清理代码的方法论,涵盖从静态分析验证到渐进式移除的全流程策略。我们特别关注如何在大型团队协作环境中,平衡清理效率与系统稳定性,避免因代码清理引入新的问题。

一、静态分析阶段

静态分析是代码清理的第一步,也是确保安全移除的基础。通过自动化工具和人工审查相结合的方式,我们可以准确识别出哪些代码是真正可以安全移除的。

1.1 分析工具链配置

推荐工具组合:

代码语言:javascript
代码运行次数:0
运行
复制
// package.json 
{
  "scripts": {
    "analyze": "eslint --ext .js,.jsx,.ts,.tsx --report-unused-disable-directives src",
    "type-check": "tsc --noEmit --listFiles",
    "bundle-report": "webpack-bundle-analyzer stats.json"
  },
  "devDependencies": {
    "eslint-plugin-unused-imports": "^2.0.0",
    "ts-prune": "^1.3.0",
    "webpack-bundle-analyzer": "^4.7.0"
  }
}

架构解析:

  • ESLint:通过no-unused-vars规则和unused-imports插件检测未使用的变量和导入
  • TypeScript:利用类型系统追踪类型引用关系,ts-prune可找出未被导出的类型定义
  • Webpack:通过依赖分析确认运行时实际加载的模块

1.2 安全验证流程

代码语言:javascript
代码运行次数:0
运行
复制
// 依赖关系分析工具
class DependencyAnalyzer {
  constructor() {
    this.dependencies = new Map(); // 存储依赖关系
    this.references = new Map();   // 存储引用关系
    this.entryPoints = new Set();  // 存储入口点
  }
  
  /**
   * 分析模块依赖关系
   * @param {string} filePath - 文件路径
   * @param {string} content - 文件内容
   */
  analyzeFile(filePath, content) {
    // 解析ES6导入语句
    const importRegex = /import\s+(?:{([^}]+)}|(\w+)|\*\s+as\s+(\w+))\s+from\s+['"]([^'"]+)['"]/g;
    let match;
    
    while ((match = importRegex.exec(content)) !== null) {
      const [, namedImports, defaultImport, namespaceImport, source] = match;
      const imports = [];
      
      if (namedImports) {
        imports.push(...namedImports.split(',').map(i => i.trim()));
      }
      
      if (defaultImport) {
        imports.push('default');
      }
      
      if (namespaceImport) {
        imports.push('*');
      }
      
      this.addDependency(filePath, source, imports);
    }
    
    // 解析CommonJS require语句
    const requireRegex = /require\(['"]([^'"]+)['"]\)/g;
    while ((match = requireRegex.exec(content)) !== null) {
      this.addDependency(filePath, match[1], ['*']);
    }
  }
  
  /**
   * 添加依赖关系
   * @param {string} from - 源文件
   * @param {string} to - 目标文件
   * @param {Array} imports - 导入的符号
   */
  addDependency(from, to, imports) {
    if (!this.dependencies.has(from)) {
      this.dependencies.set(from, new Map());
    }
    
    const fileDeps = this.dependencies.get(from);
    if (!fileDeps.has(to)) {
      fileDeps.set(to, new Set());
    }
    
    imports.forEach(importItem => fileDeps.get(to).add(importItem));
    
    // 建立反向引用关系
    if (!this.references.has(to)) {
      this.references.set(to, new Set());
    }
    this.references.get(to).add(from);
  }
  
  /**
   * 查找未使用的导出
   * @param {string} filePath - 文件路径
   * @returns {Array} 未使用的导出列表
   */
  findUnusedExports(filePath) {
    const content = this.getFileContent(filePath);
    const exports = this.parseExports(content);
    const usedExports = this.getUsedExports(filePath);
    
    return exports.filter(exp => !usedExports.has(exp));
  }
  
  /**
   * 解析文件中的导出语句
   * @param {string} content - 文件内容
   * @returns {Array} 导出的符号列表
   */
  parseExports(content) {
    const exports = [];
    
    // 解析ES6命名导出
    const namedExportRegex = /export\s+{([^}]+)}/g;
    let match;
    while ((match = namedExportRegex.exec(content)) !== null) {
      const items = match[1].split(',').map(item => {
        const parts = item.trim().split(/\s+as\s+/);
        return parts[parts.length - 1]; // 返回导出名称
      });
      exports.push(...items);
    }
    
    // 解析默认导出
    if (/export\s+default/.test(content)) {
      exports.push('default');
    }
    
    // 解析变量导出
    const varExportRegex = /export\s+(?:const|let|var|function|class)\s+(\w+)/g;
    while ((match = varExportRegex.exec(content)) !== null) {
      exports.push(match[1]);
    }
    
    return [...new Set(exports)]; // 去重
  }
  
  /**
   * 获取被使用的导出
   * @param {string} filePath - 文件路径
   * @returns {Set} 被使用的导出集合
   */
  getUsedExports(filePath) {
    const usedExports = new Set();
    const referencingFiles = this.references.get(filePath) || [];
    
    referencingFiles.forEach(refFile => {
      const deps = this.dependencies.get(refFile);
      if (deps && deps.has(filePath)) {
        deps.get(filePath).forEach(exportName => {
          usedExports.add(exportName);
        });
      }
    });
    
    return usedExports;
  }
}

使用示例:

代码语言:javascript
代码运行次数:0
运行
复制
const analyzer = new DependencyAnalyzer();
// 分析项目中的所有文件
projectFiles.forEach(file => {
  analyzer.analyzeFile(file.path, file.content);
});

// 查找未使用的导出
const unusedExports = projectFiles
  .map(file => ({
    file: file.path,
    unused: analyzer.findUnusedExports(file.path)
  }))
  .filter(result => result.unused.length > 0);

架构解析: 模块依赖关系分析器,用于分析代码文件之间的依赖关系并识别未使用的导出。

设计思路: 通过解析代码中的导入导出语句,建立完整的依赖图,从而识别哪些导出从未被使用过。

重点逻辑:

  • 解析ES6和CommonJS导入语句。
  • 建立文件间的依赖关系图。
  • 解析导出语句并跟踪使用情况。
  • 识别未被任何文件引用的导出。

参数解析:

  • dependencies: 存储文件依赖关系的Map。
  • references: 存储反向引用关系的Map。
  • filePath: 文件路径。
  • content: 文件内容字符串。
  • imports: 导入的符号数组。

二、渐进式清理流程

为了确保代码移除的安全性,我们需要采用渐进式的清理流程,而不是一次性删除所有可疑代码。

2.1 标记可疑代码

代码语言:javascript
代码运行次数:0
运行
复制
// 代码标记系统
class CodeMarker {
  constructor() {
    this.markers = new Map(); // 存储代码标记
    this.markerTypes = {
      DEPRECATED: 'deprecated',     // 已废弃
      UNUSED: 'unused',             // 未使用
      REDUNDANT: 'redundant',       // 冗余代码
      EXPERIMENTAL: 'experimental'  // 实验性代码
    };
  }
  
  /**
   * 标记代码片段
   * @param {Object} options - 标记选项
   * @param {string} options.filePath - 文件路径
   * @param {number} options.lineStart - 起始行号
   * @param {number} options.lineEnd - 结束行号
   * @param {string} options.type - 标记类型
   * @param {string} options.reason - 标记原因
   * @param {string} options.replacement - 替代方案
   */
  markCode(options) {
    const {
      filePath,
      lineStart,
      lineEnd,
      type,
      reason,
      replacement,
      markedBy,
      markedAt
    } = options;
    
    const markerId = `${filePath}:${lineStart}-${lineEnd}`;
    const marker = {
      id: markerId,
      filePath,
      lineStart,
      lineEnd,
      type,
      reason,
      replacement,
      markedBy: markedBy || this.getCurrentUser(),
      markedAt: markedAt || new Date().toISOString(),
      status: 'pending' // pending, approved, rejected, removed
    };
    
    this.markers.set(markerId, marker);
    this.addInlineComment(filePath, lineStart, this.generateMarkerComment(marker));
    
    return markerId;
  }
  
  /**
   * 生成标记注释
   * @param {Object} marker - 标记对象
   * @returns {string} 注释内容
   */
  generateMarkerComment(marker) {
    const comments = [
      `@deprecated ${marker.reason}`,
      `@marker-type ${marker.type}`,
      `@marked-by ${marker.markedBy}`,
      `@marked-at ${marker.markedAt}`
    ];
    
    if (marker.replacement) {
      comments.push(`@replacement ${marker.replacement}`);
    }
    
    if (marker.type === this.markerTypes.DEPRECATED) {
      comments.push('@todo Remove in next major version');
    }
    
    return comments.join('\n * ');
  }
  
  /**
   * 在代码中添加内联注释
   * @param {string} filePath - 文件路径
   * @param {number} lineNumber - 行号
   * @param {string} comment - 注释内容
   */
  addInlineComment(filePath, lineNumber, comment) {
    const fileContent = this.readFile(filePath);
    const lines = fileContent.split('\n');
    
    // 在指定行前添加注释
    const commentBlock = [
      '/**',
      ` * ${comment}`,
      ' */'
    ].join('\n');
    
    lines.splice(lineNumber - 1, 0, commentBlock);
    
    this.writeFile(filePath, lines.join('\n'));
  }
  
  /**
   * 批量标记文件中的导出
   * @param {string} filePath - 文件路径
   * @param {Array} exports - 导出列表
   * @param {Object} options - 标记选项
   */
  markExports(filePath, exports, options) {
    const fileContent = this.readFile(filePath);
    const lines = fileContent.split('\n');
    
    exports.forEach(exp => {
      const exportLine = this.findExportLine(lines, exp);
      if (exportLine !== -1) {
        this.markCode({
          ...options,
          filePath,
          lineStart: exportLine,
          lineEnd: exportLine
        });
      }
    });
  }
  
  /**
   * 查找导出语句所在的行号
   * @param {Array} lines - 代码行数组
   * @param {string} exportName - 导出名称
   * @returns {number} 行号,未找到返回-1
   */
  findExportLine(lines, exportName) {
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      // 匹配各种导出模式
      if (
        line.includes(`export ${exportName}`) ||
        line.includes(`export { ${exportName}`) ||
        line.includes(`export {.*${exportName}.*}`) ||
        line.includes(`export default ${exportName}`)
      ) {
        return i + 1; // 行号从1开始
      }
    }
    return -1;
  }
  
  /**
   * 获取特定状态的标记
   * @param {string} status - 状态
   * @returns {Array} 标记列表
   */
  getMarkersByStatus(status) {
    return Array.from(this.markers.values())
      .filter(marker => marker.status === status);
  }
  
  /**
   * 更新标记状态
   * @param {string} markerId - 标记ID
   * @param {string} status - 新状态
   * @param {string} reviewedBy - 审核人
   */
  updateMarkerStatus(markerId, status, reviewedBy) {
    if (this.markers.has(markerId)) {
      const marker = this.markers.get(markerId);
      marker.status = status;
      marker.reviewedBy = reviewedBy;
      marker.reviewedAt = new Date().toISOString();
    }
  }
}

使用示例:

代码语言:javascript
代码运行次数:0
运行
复制
const marker = new CodeMarker();

// 标记废弃的函数
marker.markCode({
  filePath: 'src/utils/helpers.js',
  lineStart: 15,
  lineEnd: 25,
  type: marker.markerTypes.DEPRECATED,
  reason: 'Replaced by new implementation with better performance',
  replacement: 'use newHelperFunction instead',
  markedBy: 'john.doe'
});

// 批量标记未使用的导出
marker.markExports(
  'src/components/LegacyComponents.js',
  ['OldComponentA', 'OldComponentB'],
  {
    type: marker.markerTypes.UNUSED,
    reason: 'Components are no longer used in the application',
    markedBy: 'jane.smith'
  }
);

架构解析: 这是一个代码标记系统,用于在代码中添加标记注释并跟踪标记状态。

设计思路: 通过在代码中添加结构化注释,明确标识可疑代码,并通过状态管理跟踪清理进度。

重点逻辑:

  • 生成标准化的标记注释。
  • 在代码中插入内联注释。
  • 管理标记的状态流转。
  • 支持批量标记操作。

参数解析:

  • markers: 存储所有标记的Map。
  • markerTypes: 标记类型枚举。
  • options: 标记配置选项。
  • filePath: 被标记的文件路径。
  • lineStart/lineEnd: 标记的代码行范围。

2.2 创建代码墓碑

代码语言:javascript
代码运行次数:0
运行
复制
// 代码墓碑系统
class CodeTombstone {
  constructor() {
    this.tombstones = new Map(); // 存储墓碑记录
    this.tombstoneDir = '.tombstones'; // 墓碑文件存储目录
  }
  
  /**
   * 为即将删除的代码创建墓碑
   * @param {Object} codeInfo - 代码信息
   * @returns {string} 墓碑ID
   */
  createTombstone(codeInfo) {
    const {
      filePath,
      codeSnippet,
      deletionReason,
      deprecatedSince,
      createdBy,
      relatedIssues
    } = codeInfo;
    
    const tombstoneId = this.generateTombstoneId(filePath);
    const tombstone = {
      id: tombstoneId,
      filePath,
      fileName: this.getFileName(filePath),
      codeSnippet: this.extractCodeSnippet(codeSnippet),
      deletionReason,
      deprecatedSince: deprecatedSince || new Date().toISOString(),
      createdAt: new Date().toISOString(),
      createdBy: createdBy || this.getCurrentUser(),
      relatedIssues: relatedIssues || [],
      status: 'pending', // pending, approved, deleted
      backupPath: this.createBackup(codeSnippet, tombstoneId)
    };
    
    this.tombstones.set(tombstoneId, tombstone);
    this.saveTombstoneToFile(tombstone);
    
    return tombstoneId;
  }
  
  /**
   * 生成墓碑ID
   * @param {string} filePath - 文件路径
   * @returns {string} 墓碑ID
   */
  generateTombstoneId(filePath) {
    const fileName = this.getFileName(filePath);
    const timestamp = Date.now();
    return `${fileName}_${timestamp}`;
  }
  
  /**
   * 提取文件名
   * @param {string} filePath - 文件路径
   * @returns {string} 文件名
   */
  getFileName(filePath) {
    return filePath.split('/').pop().split('.')[0];
  }
  
  /**
   * 提取代码片段摘要
   * @param {string} code - 代码内容
   * @returns {string} 代码摘要
   */
  extractCodeSnippet(code) {
    // 提取代码的前几行作为摘要
    const lines = code.split('\n');
    const summaryLines = lines.slice(0, 5);
    return {
      full: code,
      summary: summaryLines.join('\n'),
      lineCount: lines.length
    };
  }
  
  /**
   * 创建代码备份
   * @param {string} code - 代码内容
   * @param {string} tombstoneId - 墓碑ID
   * @returns {string} 备份文件路径
   */
  createBackup(code, tombstoneId) {
    const backupPath = `${this.tombstoneDir}/backups/${tombstoneId}.backup.js`;
    this.writeFile(backupPath, code);
    return backupPath;
  }
  
  /**
   * 保存墓碑记录到文件
   * @param {Object} tombstone - 墓碑对象
   */
  saveTombstoneToFile(tombstone) {
    const tombstonePath = `${this.tombstoneDir}/records/${tombstone.id}.json`;
    this.writeFile(tombstonePath, JSON.stringify(tombstone, null, 2));
  }
  
  /**
   * 获取墓碑记录
   * @param {string} tombstoneId - 墓碑ID
   * @returns {Object} 墓碑对象
   */
  getTombstone(tombstoneId) {
    if (this.tombstones.has(tombstoneId)) {
      return this.tombstones.get(tombstoneId);
    }
    
    // 从文件系统加载
    const tombstonePath = `${this.tombstoneDir}/records/${tombstoneId}.json`;
    if (this.fileExists(tombstonePath)) {
      const content = this.readFile(tombstonePath);
      const tombstone = JSON.parse(content);
      this.tombstones.set(tombstoneId, tombstone);
      return tombstone;
    }
    
    return null;
  }
  
  /**
   * 列出所有墓碑
   * @param {Object} filter - 过滤条件
   * @returns {Array} 墓碑列表
   */
  listTombstones(filter = {}) {
    let tombstones = Array.from(this.tombstones.values());
    
    // 应用过滤条件
    if (filter.status) {
      tombstones = tombstones.filter(t => t.status === filter.status);
    }
    
    if (filter.createdBy) {
      tombstones = tombstones.filter(t => t.createdBy === filter.createdBy);
    }
    
    if (filter.dateRange) {
      const { start, end } = filter.dateRange;
      tombstones = tombstones.filter(t => {
        const createdAt = new Date(t.createdAt);
        return createdAt >= start && createdAt <= end;
      });
    }
    
    return tombstones;
  }
  
  /**
   * 批准墓碑(准备删除)
   * @param {string} tombstoneId - 墓碑ID
   * @param {string} approvedBy - 批准人
   */
  approveTombstone(tombstoneId, approvedBy) {
    const tombstone = this.getTombstone(tombstoneId);
    if (tombstone) {
      tombstone.status = 'approved';
      tombstone.approvedBy = approvedBy;
      tombstone.approvedAt = new Date().toISOString();
      this.saveTombstoneToFile(tombstone);
    }
  }
  
  /**
   * 恢复已删除的代码
   * @param {string} tombstoneId - 墓碑ID
   * @returns {boolean} 是否恢复成功
   */
  restoreCode(tombstoneId) {
    const tombstone = this.getTombstone(tombstoneId);
    if (!tombstone || tombstone.status !== 'deleted') {
      return false;
    }
    
    try {
      const backupContent = this.readFile(tombstone.backupPath);
      this.writeFile(tombstone.filePath, backupContent);
      
      tombstone.status = 'restored';
      tombstone.restoredAt = new Date().toISOString();
      this.saveTombstoneToFile(tombstone);
      
      return true;
    } catch (error) {
      console.error('Failed to restore code:', error);
      return false;
    }
  }
  
  /**
   * 生成墓碑报告
   * @param {Object} options - 报告选项
   * @returns {string} 报告内容
   */
  generateReport(options = {}) {
    const tombstones = this.listTombstones(options.filter || {});
    
    const report = {
      generatedAt: new Date().toISOString(),
      total: tombstones.length,
      byStatus: this.groupBy(tombstones, 'status'),
      byCreator: this.groupBy(tombstones, 'createdBy'),
      byReason: this.groupBy(tombstones, 'deletionReason'),
      recent: tombstones
        .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
        .slice(0, 10)
    };
    
    return report;
  }
  
  /**
   * 按属性分组
   * @param {Array} array - 数组
   * @param {string} key - 分组键
   * @returns {Object} 分组结果
   */
  groupBy(array, key) {
    return array.reduce((result, item) => {
      const groupKey = item[key];
      if (!result[groupKey]) {
        result[groupKey] = [];
      }
      result[groupKey].push(item);
      return result;
    }, {});
  }
}

使用示例:

代码语言:javascript
代码运行次数:0
运行
复制
const tombstone = new CodeTombstone();

// 为废弃组件创建墓碑
tombstone.createTombstone({
  filePath: 'src/components/LegacyChart.js',
  codeSnippet: legacyChartCode,
  deletionReason: 'Replaced by new charting library',
  deprecatedSince: '2023-06-01',
  createdBy: 'john.doe',
  relatedIssues: ['#1234', '#5678']
});

// 批准删除
tombstone.approveTombstone('LegacyChart_1689876543210', 'jane.smith');

// 生成报告
const report = tombstone.generateReport({
  filter: {
    status: 'approved',
    dateRange: {
      start: new Date('2023-07-01'),
      end: new Date()
    }
  }
});

架构解析: 代码墓碑系统,用于记录即将删除的代码信息并提供恢复机制。

设计思路: 通过创建墓碑记录,保存被删除代码的完整信息和备份,确保在需要时可以恢复。

重点逻辑:

  • 创建包含代码信息的墓碑记录。
  • 备份被删除的代码内容。
  • 管理墓碑状态和生命周期。
  • 提供代码恢复功能。

参数解析:

  • tombstones: 存储墓碑记录的Map。
  • tombstoneDir: 墓碑文件存储目录。
  • codeInfo: 被删除代码的信息。
  • tombstoneId: 墓碑唯一标识符。

2.3 安全移除策略

四阶段移除法:

配套工具:

代码语言:javascript
代码运行次数:0
运行
复制
// scripts/removeDeadCode.js
const { execSync } = require('child_process');
const fs = require('fs');

function safeRemove(filePath) {
  try {
    // 1. 检查git历史
    const lastEdit = execSync(`git log -1 --format="%ai" -- ${filePath}`).toString();
    
    // 2. 检查引用
    const refs = execSync(`grep -r "${filePath}" src/`).toString();
    
    if (!refs.trim()) {
      // 3. 创建备份
      fs.copyFileSync(filePath, `archive/${filePath}`);
      
      // 4. 实际删除
      fs.unlinkSync(filePath);
      console.log(`✅ 安全删除: ${filePath}`);
    } else {
      console.log(`⚠️ 存在引用: ${filePath}`);
    }
  } catch (error) {
    console.error(`❌ 删除失败: ${filePath}`, error);
  }
}

2.4 工程化配套方案

CI/CD集成:

代码语言:javascript
代码运行次数:0
运行
复制
# .github/workflows/code-cleanup.yml
name: Code Cleanup Check

on:
  schedule:
    - cron: '0 0 * * 1' # 每周一运行
  pull_request:
    paths:
      - 'src/**'

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm install
      - run: npm run analyze
      - name: Check for dead code
        run: |
          DEAD_FILES=$(ts-prune | grep -v 'used in module')
          if [ -n "$DEAD_FILES" ]; then
            echo "::warning::发现未使用代码: $DEAD_FILES"
            exit 1
          fi

架构决策记录模板:

代码语言:javascript
代码运行次数:0
运行
复制
# ADR-042: 移除旧版表单系统

## 状态
✅ 已批准

## 背景
自v5.0.0引入新的Formik集成后,旧版表单...

## 决策
- 在v5.3.0标记为废弃
- 计划在v6.0.0完全移除
- 提供迁移工具`formMigrator.js`

## 影响
- 需要更新15个页面的表单
- 影响3个自定义表单控件
- 减少打包体积约23KB

三、最佳实践建议

建立有效的代码清理最佳实践,确保整个流程的可持续性和可靠性。

3.1 时间窗口法

代码语言:javascript
代码运行次数:0
运行
复制
// 时间窗口管理器
class TimeWindowManager {
  constructor() {
    this.windows = new Map();
  }
  
  /**
   * 创建时间窗口
   * @param {string} name - 窗口名称
   * @param {Object} config - 配置
   */
  createTimeWindow(name, config) {
    const {
      retentionPeriod = '30d',  // 保留期
      warningPeriod = '7d',    // 警告期
      gracePeriod = '3d',      // 宽限期
      reviewRequired = true    // 是否需要审核
    } = config;
    
    const window = {
      name,
      retentionPeriod: this.parseDuration(retentionPeriod),
      warningPeriod: this.parseDuration(warningPeriod),
      gracePeriod: this.parseDuration(gracePeriod),
      reviewRequired,
      createdAt: new Date().toISOString(),
      items: new Set()
    };
    
    this.windows.set(name, window);
    return window;
  }
  
  /**
   * 将项目添加到时间窗口
   * @param {string} windowName - 窗口名称
   * @param {Object} item - 项目
   */
  addItem(windowName, item) {
    const window = this.windows.get(windowName);
    if (!window) {
      throw new Error(`Time window ${windowName} not found`);
    }
    
    const windowItem = {
      ...item,
      addedAt: new Date().toISOString(),
      status: 'pending',
      eligibleForRemovalAt: this.calculateEligibleDate(window, item)
    };
    
    window.items.add(windowItem);
    return windowItem;
  }
  
  /**
   * 计算可移除日期
   * @param {Object} window - 时间窗口
   * @param {Object} item - 项目
   * @returns {string} 可移除日期ISO字符串
   */
  calculateEligibleDate(window, item) {
    const addedAt = new Date(item.addedAt || new Date());
    const eligibleDate = new Date(addedAt);
    eligibleDate.setDate(eligibleDate.getDate() + window.retentionPeriod);
    return eligibleDate.toISOString();
  }
  
  /**
   * 检查项目是否可以移除
   * @param {string} windowName - 窗口名称
   * @param {Object} item - 项目
   * @returns {Object} 检查结果
   */
  checkRemovalEligibility(windowName, item) {
    const window = this.windows.get(windowName);
    if (!window) {
      return { eligible: false, reason: 'Window not found' };
    }
    
    const now = new Date();
    const eligibleDate = new Date(item.eligibleForRemovalAt);
    
    if (now < eligibleDate) {
      const daysLeft = Math.ceil((eligibleDate - now) / (1000 * 60 * 60 * 24));
      return { 
        eligible: false, 
        reason: `Retention period not expired (${daysLeft} days left)` 
      };
    }
    
    // 检查是否在警告期内
    const warningEndDate = new Date(eligibleDate);
    warningEndDate.setDate(warningEndDate.getDate() + window.warningPeriod);
    
    if (now < warningEndDate) {
      return { 
        eligible: false, 
        reason: 'In warning period - manual review required',
        warning: true
      };
    }
    
    // 检查是否在宽限期内
    const graceEndDate = new Date(warningEndDate);
    graceEndDate.setDate(graceEndDate.getDate() + window.gracePeriod);
    
    if (now < graceEndDate) {
      return { 
        eligible: true, 
        gracePeriod: true,
        message: 'In grace period - proceed with caution'
      };
    }
    
    return { eligible: true, message: 'Eligible for removal' };
  }
  
  /**
   * 获取可移除的项目
   * @param {string} windowName - 窗口名称
   * @returns {Array} 可移除项目列表
   */
  getEligibleItems(windowName) {
    const window = this.windows.get(windowName);
    if (!window) return [];
    
    const now = new Date();
    const eligibleItems = [];
    
    for (const item of window.items) {
      const checkResult = this.checkRemovalEligibility(windowName, item);
      if (checkResult.eligible) {
        eligibleItems.push({
          item,
          checkResult
        });
      }
    }
    
    return eligibleItems;
  }
  
  /**
   * 解析持续时间
   * @param {string} duration - 持续时间字符串
   * @returns {number} 天数
   */
  parseDuration(duration) {
    const match = duration.match(/^(\d+)([dwm])$/);
    if (!match) {
      throw new Error(`Invalid duration format: ${duration}`);
    }
    
    const value = parseInt(match[1]);
    const unit = match[2];
    
    switch (unit) {
      case 'd': return value;
      case 'w': return value * 7;
      case 'm': return value * 30;
      default: throw new Error(`Unknown time unit: ${unit}`);
    }
  }
  
  /**
   * 生成时间窗口报告
   * @param {string} windowName - 窗口名称
   * @returns {Object} 报告
   */
  generateReport(windowName) {
    const window = this.windows.get(windowName);
    if (!window) return null;
    
    const now = new Date();
    const report = {
      windowName,
      generatedAt: now.toISOString(),
      totalItems: window.items.size,
      byStatus: {},
      byEligibility: {
        eligible: 0,
        notEligible: 0,
        warningPeriod: 0,
        gracePeriod: 0
      }
    };
    
    for (const item of window.items) {
      // 按状态统计
      if (!report.byStatus[item.status]) {
        report.byStatus[item.status] = 0;
      }
      report.byStatus[item.status]++;
      
      // 按可移除性统计
      const eligibility = this.checkRemovalEligibility(windowName, item);
      if (eligibility.eligible) {
        if (eligibility.gracePeriod) {
          report.byEligibility.gracePeriod++;
        } else {
          report.byEligibility.eligible++;
        }
      } else {
        if (eligibility.warning) {
          report.byEligibility.warningPeriod++;
        } else {
          report.byEligibility.notEligible++;
        }
      }
    }
    
    return report;
  }
}

// 使用示例
const timeWindowManager = new TimeWindowManager();

// 创建标准清理窗口
timeWindowManager.createTimeWindow('standard-cleanup', {
  retentionPeriod: '30d',
  warningPeriod: '7d',
  gracePeriod: '3d',
  reviewRequired: true
});

// 添加待清理项目
timeWindowManager.addItem('standard-cleanup', {
  id: 'legacy-component-123',
  type: 'component',
  filePath: 'src/components/LegacyComponent.js',
  reason: 'Replaced by new implementation'
});

// 检查可移除性
const eligibility = timeWindowManager.checkRemovalEligibility(
  'standard-cleanup', 
  { id: 'legacy-component-123' }
);

console.log('Removal eligibility:', eligibility);

架构解析: 这是一个时间窗口管理器,用于管理代码清理的时间策略。

设计思路: 通过设置不同的时间阶段(保留期、警告期、宽限期),确保代码在被移除前有足够的时间进行验证。

重点逻辑:

  • 管理多个时间窗口配置。
  • 计算项目的可移除时间
  • 分阶段检查移除资格。
  • 生成统计报告。

参数解析:

  • windows: 存储时间窗口的Map。
  • retentionPeriod: 保留期(天数)。
  • warningPeriod: 警告期(天数)。
  • gracePeriod: 宽限期(天数)。

3.2 文档同步策略

代码语言:javascript
代码运行次数:0
运行
复制
const { appendFileSync, readFileSync } = require('fs');

/**
 * 更新变更日志和类型定义文件,标记组件为已弃用
 * @param {Object} deprecation - 弃用信息对象
 * @param {string} deprecation.component - 被弃用的组件名称
 * @param {string} [deprecation.alternative] - 替代方案
 * @param {string} deprecation.removeAfter - 计划移除时间
 * @returns {void}
 */
function updateChangelog(deprecation) {
  const entry = `\n## ${new Date().toISOString().split('T')[0]}
- Deprecated: ${deprecation.component}
- Alternative: ${deprecation.alternative || 'None'}
- Removal planned: ${deprecation.removeAfter}\n`;

  appendFileSync('CHANGELOG.md', entry);

  // 更新类型定义
  const types = readFileSync('types.d.ts', 'utf8');
  const updated = types.replace(`interface ${deprecation.component}`, `/** @deprecated */\ninterface ${deprecation.component}`);
  writeFileSync('types.d.ts', updated);
}

同步策略:

在项目废弃某个组件时,自动更新项目的文档和类型定义,确保:

  • 变更日志记录了废弃信息。
  • 类型定义文件明确标记了废弃状态。
  • 开发者能清楚地知道替代方案和计划移除时间。

功能解析:

  • 导入依赖:从Node.js的fs模块中导入三个同步文件操作方法:
    • appendFileSync: 向文件末尾追加内容。
    • readFileSync: 同步读取文件内容。
    • writeFileSync: 同步写入文件内容。
  • updateChangelog函数:这个函数接收一个deprecation对象作为参数,该对象包含以下属性:
    • component: 被废弃的组件/接口名称。
    • alternative: 替代方案(可选)。
    • removeAfter: 计划移除的时间。
  • 生成变更日志条目:创建一个Markdown格式的字符串,包含:
    • 二级标题(当前日期,格式为YYYY-MM-DD)。
    • 被废弃的组件。
    • 替代方案(如果没有则显示"None")。
    • 计划移除时间。
  • 追加到变更日志:将生成的条目追加到CHANGELOG.md文件末尾
  • 更新类型定义:
    • 读取types.d.ts文件内容。
    • 在被废弃的接口前添加/** @deprecated */的JSDoc注释。
    • 将修改后的内容写回原文件。

3.3 团队通知机制

站会提醒模板:

代码语言:javascript
代码运行次数:0
运行
复制
**代码清理通知** 📢

即将移除的组件:
- `OldModal` (替换为 `NewDialog`)
  - 影响文件:8处
  - 最后使用:2023-11-15
  - 移除日期:2023-12-01

需要行动:
1. 检查你的功能分支
2. 更新测试用例
3. 报告任何问题

结语

通过本文的系统化方法,我们建立了从代码发现安全移除的完整生命周期管理:

  • 验证先行:通过多维度静态分析确保清理安全性。
  • 渐进过渡:采用标记→观察→迁移→移除的四阶段模型。
  • 工程保障:工具链支持自动化检测和删除。
  • 团队协作:完善的文档和通知机制。

好的代码清理不是一次性任务,而是持续的质量实践

建议将本文方法集成到日常开发流程中,定期执行"代码卫生检查",保持代码库的健康状态。当每个团队成员都具备代码清理意识时,项目就能在快速迭代中维持长期的可维护性。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 一、静态分析阶段
    • 1.1 分析工具链配置
    • 1.2 安全验证流程
  • 二、渐进式清理流程
    • 2.1 标记可疑代码
    • 2.2 创建代码墓碑
    • 2.3 安全移除策略
    • 2.4 工程化配套方案
  • 三、最佳实践建议
    • 3.1 时间窗口法
    • 3.2 文档同步策略
    • 3.3 团队通知机制
  • 结语
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档