人人都会AI编程

23.5 代码质量度量与重构原则

更新时间:2026-07-11

代码写出来能跑,只是起点。真正区分专业团队与临时拼凑项目的,是它们如何持续维护和改进代码质量。这一节不讨论抽象的“优雅代码”概念,而是聚焦于可落地的度量指标和实用重构手法。

23.5.1 可度量的质量指标

代码质量不是玄学,它可以通过一系列客观指标来衡量:

圈复杂度(Cyclomatic Complexity)
衡量一个函数中逻辑分支的数量。每个 ifelse ifforwhilecase 都会增加圈复杂度。一般认为,函数的圈复杂度不应超过 10,超过 15 就强烈建议重构。

// 圈复杂度 = 4 (if, else if, else if 各一个分支, 加上默认路径)
function getGrade(score) {
  if (score >= 90) return 'A';
  else if (score >= 80) return 'B';
  else if (score >= 70) return 'C';
  return 'D';
}

认知复杂度(Cognitive Complexity)
相比圈复杂度只统计分支,认知复杂度更关注代码被人类理解的难易程度。它会惩罚嵌套深度、中断逻辑流的运算符(如 breakcontinue)、复杂的布尔表达式等。现代 Lint 工具(如 ESLint 的 sonarjs/cognitive-complexity 规则)可以自动检查。

代码重复率
“重复是万恶之源”。同一个逻辑块出现三次以上,就应当抽取为公共函数或组件。工具如 jscpd 可以扫描项目中的重复代码,给出量化报告。

函数长度与参数个数
虽然没有绝对的标准,但一个函数超过 50 行、参数超过 4 个,通常意味着它在做太多事情,需要拆解。

测试覆盖率
语句覆盖率、分支覆盖率、函数覆盖率等指标反映了代码被测试保护的程度。覆盖率不是越高越好,但过低的覆盖率意味着大量代码处于“裸奔”状态。

23.5.2 自动化度量工具

不需要人工计算这些指标,现代工具链已经可以自动完成:

  • ESLint 配合 complexitysonarjs 插件,可以直接在开发时给出复杂度警告。
  • SonarQube 提供全面的代码质量报告,包括复杂度、重复率、潜在 bug 等,是许多团队 CI/CD 流水线中的标准环节。
  • CodeClimate、Codecov 等在线服务可以自动分析每次提交的质量变化趋势。

关键是将这些工具接入持续集成流程,而不是依赖开发者自觉手动运行。质量度量只有自动化和常态化,才能真正落地。

23.5.3 重构的指导原则

重构(Refactoring)是在不改变代码外部行为的前提下,改善其内部结构。以下原则来自 Martin Fowler 的经典理论和实战经验:

1. 三步律:小步前进,频繁验证

  • 先写测试(确保当前行为已覆盖)
  • 进行一次小改动(如提取一个变量、简化一个条件)
  • 运行测试,确保一切绿色
  • 提交代码

不要一次性改太多东西,否则出了问题很难定位。

2. 单一职责:一个函数只做一件事

如果你需要“和”来命名函数(如 getUserAndUpdateProfile),它很可能做了两件事。拆成 getUserupdateProfile 两个独立函数,再在一个组合函数中调用。

3. 尽早返回:减少嵌套

// 重构前:深层嵌套
function process(data) {
  if (data) {
    if (data.isValid) {
      if (data.hasPermission) {
        return execute(data);
      }
    }
  }
  return null;
}

// 重构后:尽早返回
function process(data) {
  if (!data || !data.isValid || !data.hasPermission) return null;
  return execute(data);
}

4. 消除魔法数字与字符串

任何在代码中直接出现的、没有明确含义的值,都应该被抽取为命名常量。

// 坏味道:魔法数字
if (user.age >= 18) { ... }

// 重构后
const LEGAL_ADULT_AGE = 18;
if (user.age >= LEGAL_ADULT_AGE) { ... }

5. 用实意命名取代注释

注释往往是在为糟糕的命名“打补丁”。如果能用函数名、变量名直接表达意图,就不需要注释。

// 重构前:依赖注释解释
// check if user is eligible for promotion
if (user.points > 1000 && user.monthsActive > 6 && !user.isBlocked) { ... }

// 重构后:意图直接体现在变量名
const isEligibleForPromotion = user.points > 1000 && user.monthsActive > 6 && !user.isBlocked;
if (isEligibleForPromotion) { ... }

6. 避免过度重构

不是所有代码都值得重构。如果某段代码运行良好、很少修改、没有引起 bug,强行“优化”只会带来风险。重构的黄金时机是:添加新功能前发现现有代码难以扩展,或修复 bug 时发现代码结构混乱导致难以定位问题。

23.5.4 重构实战:一个常见案例

假设项目中有一个处理订单的函数已变得臃肿:

function processOrder(order) {
  // 验证
  if (!order.items || order.items.length === 0) { return 'no items'; }
  if (!order.user) { return 'no user'; }
  // 计算价格
  let total = 0;
  for (let item of order.items) {
    total += item.price * item.quantity;
  }
  if (order.coupon) { total *= 0.9; }
  // 更新库存... 
  // 发送邮件...
  // 超过100行了
}

重构步骤(小步快跑)

  1. 提取验证逻辑 → validateOrder(order)
  2. 提取价格计算 → calculateTotal(items, coupon)
  3. 提取库存更新 → updateInventory(items)
  4. 提取邮件发送 → sendConfirmationEmail(user)
  5. processOrder 变成对上述函数的清晰编排

每次提取后运行测试,确保行为未变。重构完成后,processOrder 从 100 行变成十几行的“指挥中心”,每个子函数都可以独立测试和复用。

23.5.5 度量与重构的良性循环

度量指标指导重构方向,重构反过来改善度量指标。在实际工作中,可以建立这样一个习惯:

  1. 每次接手一段代码时,用工具看一下它的圈复杂度和认知复杂度。
  2. 如果复杂度超标,在修改之前先进行安全的小步重构。
  3. 重构后确认复杂度下降,行为仍正确,然后再添加新功能或修复 bug。

这种 “童子军规则”(离开营地时让它比你来时更干净) 的实践,能让代码质量在日积月累中稳步提升,而不是依赖于大规模的重写。