代码写出来能跑,只是起点。真正区分专业团队与临时拼凑项目的,是它们如何持续维护和改进代码质量。这一节不讨论抽象的“优雅代码”概念,而是聚焦于可落地的度量指标和实用重构手法。
23.5.1 可度量的质量指标
代码质量不是玄学,它可以通过一系列客观指标来衡量:
圈复杂度(Cyclomatic Complexity)
衡量一个函数中逻辑分支的数量。每个 if、else if、for、while、case 都会增加圈复杂度。一般认为,函数的圈复杂度不应超过 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)
相比圈复杂度只统计分支,认知复杂度更关注代码被人类理解的难易程度。它会惩罚嵌套深度、中断逻辑流的运算符(如 break、continue)、复杂的布尔表达式等。现代 Lint 工具(如 ESLint 的 sonarjs/cognitive-complexity 规则)可以自动检查。
代码重复率
“重复是万恶之源”。同一个逻辑块出现三次以上,就应当抽取为公共函数或组件。工具如 jscpd 可以扫描项目中的重复代码,给出量化报告。
函数长度与参数个数
虽然没有绝对的标准,但一个函数超过 50 行、参数超过 4 个,通常意味着它在做太多事情,需要拆解。
测试覆盖率
语句覆盖率、分支覆盖率、函数覆盖率等指标反映了代码被测试保护的程度。覆盖率不是越高越好,但过低的覆盖率意味着大量代码处于“裸奔”状态。
23.5.2 自动化度量工具
不需要人工计算这些指标,现代工具链已经可以自动完成:
- ESLint 配合
complexity和sonarjs插件,可以直接在开发时给出复杂度警告。 - SonarQube 提供全面的代码质量报告,包括复杂度、重复率、潜在 bug 等,是许多团队 CI/CD 流水线中的标准环节。
- CodeClimate、Codecov 等在线服务可以自动分析每次提交的质量变化趋势。
关键是将这些工具接入持续集成流程,而不是依赖开发者自觉手动运行。质量度量只有自动化和常态化,才能真正落地。
23.5.3 重构的指导原则
重构(Refactoring)是在不改变代码外部行为的前提下,改善其内部结构。以下原则来自 Martin Fowler 的经典理论和实战经验:
1. 三步律:小步前进,频繁验证
- 先写测试(确保当前行为已覆盖)
- 进行一次小改动(如提取一个变量、简化一个条件)
- 运行测试,确保一切绿色
- 提交代码
不要一次性改太多东西,否则出了问题很难定位。
2. 单一职责:一个函数只做一件事
如果你需要“和”来命名函数(如 getUserAndUpdateProfile),它很可能做了两件事。拆成 getUser 和 updateProfile 两个独立函数,再在一个组合函数中调用。
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行了
}
重构步骤(小步快跑):
- 提取验证逻辑 →
validateOrder(order) - 提取价格计算 →
calculateTotal(items, coupon) - 提取库存更新 →
updateInventory(items) - 提取邮件发送 →
sendConfirmationEmail(user) processOrder变成对上述函数的清晰编排
每次提取后运行测试,确保行为未变。重构完成后,processOrder 从 100 行变成十几行的“指挥中心”,每个子函数都可以独立测试和复用。
23.5.5 度量与重构的良性循环
度量指标指导重构方向,重构反过来改善度量指标。在实际工作中,可以建立这样一个习惯:
- 每次接手一段代码时,用工具看一下它的圈复杂度和认知复杂度。
- 如果复杂度超标,在修改之前先进行安全的小步重构。
- 重构后确认复杂度下降,行为仍正确,然后再添加新功能或修复 bug。
这种 “童子军规则”(离开营地时让它比你来时更干净) 的实践,能让代码质量在日积月累中稳步提升,而不是依赖于大规模的重写。