Skip to content

代码质量与重构

前言

代码写出来能跑就行了吗? 你可能写过这样的代码:功能是实现了,但过了两周自己都看不懂了。或者团队里有人离职,留下一堆"只有上帝和他才能看懂"的代码。

本章带你理解什么是好代码,如何识别坏代码,以及如何安全地改进它。

这篇文章会带你学什么?

章节内容核心概念
第 1 章代码坏味道识别常见问题
第 2 章重构手法安全地改进代码
第 3 章代码审查团队协作中的质量保障
第 4 章质量度量用数据衡量代码健康度

学完本章,你将掌握识别代码问题、安全重构、以及通过团队协作持续提升代码质量的方法。


0. 全景图:代码的生命周期

在软件开发中,有一个常被忽视的事实:代码被阅读的次数远远多于被编写的次数

一段代码从诞生到退役,大致会经历这样的旅程:

代码的一生

  • 编写阶段:开发者写下第一版实现,功能跑通了,测试通过了。
  • 审查阶段:团队成员阅读代码,提出改进建议。
  • 维护阶段:修 Bug、加功能、适配新需求——这个阶段占据了代码生命周期的 80% 以上。
  • 重构阶段:当代码变得难以维护时,需要在不改变外部行为的前提下改善内部结构。
  • 退役阶段:技术迭代,旧代码被新方案替代。

Martin Fowler 在《重构》一书中说过:"任何一个傻瓜都能写出计算机能理解的代码,唯有好的程序员才能写出人类能理解的代码。"


1. 代码坏味道:识别常见问题

1.1 什么是代码坏味道?

"代码坏味道"(Code Smell)这个概念由 Kent Beck 提出,指的是代码中那些虽然不是 Bug,但暗示着更深层设计问题的特征。就像房间里有股怪味——不会立刻让你生病,但说明某个地方需要清理了。

通过下面的交互组件,识别几种最常见的代码坏味道:

代码坏味道识别器 ── 点击切换不同示例
问题代码
function processOrder(order) {
  // 验证订单... (20行)
  // 计算价格... (15行)
  // 检查库存... (10行)
  // 发送通知... (15行)
  // 更新数据库... (10行)
  // 生成报表... (10行)
  // 总计 80+ 行!
}

📏 过长函数

一个函数超过 50 行,做了太多事情,难以理解和测试。

改进建议:将大函数拆分为多个职责单一的小函数:validateOrder()、calculatePrice()、checkInventory() 等。

1.2 常见坏味道清单

坏味道症状危害
过长函数函数超过 50 行难以理解、测试和复用
魔法数字代码中直接写 86400000含义不明,修改时容易遗漏
重复代码相似逻辑出现在多处修改时必须同步多处,容易遗漏
过深嵌套超过 3 层的 if/for逻辑像迷宫,难以追踪
过长参数列表函数参数超过 4 个调用困难,容易传错顺序
上帝类一个类/模块做了太多事职责不清,牵一发动全身

核心洞察

坏味道不是"错误",而是"信号"。它告诉你:这里的设计可能需要改进。不是所有坏味道都需要立刻修复,但你需要有能力识别它们。


2. 重构手法:安全地改进代码

2.1 什么是重构?

重构(Refactoring)的定义非常精确:在不改变代码外部行为的前提下,改善其内部结构。

关键词是"不改变外部行为"。重构不是重写,不是加功能,不是修 Bug。它是对代码内部的"整理收纳"。

通过下面的组件,对比几种常见重构手法的前后变化:

重构手法对比演示 ── 选择一种手法查看前后对比
Extract Function:将一段代码从大函数中提取出来,放入一个命名清晰的新函数中。
重构前
function printReport(invoice) {
  console.log("=== 账单 ===")
  // 计算总额
  let total = 0
  for (let item of invoice.items) {
    total += item.price * item.qty
  }
  console.log(`总计: ${total}`)
}
重构后
function printReport(invoice) {
  console.log("=== 账单 ===")
  const total = calcTotal(invoice.items)
  console.log(`总计: ${total}`)
}

function calcTotal(items) {
  return items.reduce(
    (s, i) => s + i.price * i.qty, 0
  )
}
要点:提炼函数是最常用的重构手法。好的函数名就是最好的注释——如果你需要写注释解释一段代码在做什么,那它就该被提炼成函数。

2.2 常用重构手法

提炼函数(Extract Function)

这是最常用的重构手法。当一段代码可以用一个有意义的名字来概括时,就应该把它提炼成函数。

javascript
// 重构前
function printReport(data) {
  // 计算总价
  let total = 0
  for (const item of data.items) {
    total += item.price * item.qty
  }
  // 打印...
}

// 重构后
function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price * item.qty, 0)
}

function printReport(data) {
  const total = calculateTotal(data.items)
  // 打印...
}

重命名(Rename)

好的命名是最廉价也最有效的文档。当你需要写注释来解释一个变量/函数的含义时,说明它的名字不够好。

javascript
// 重构前
const d = new Date() - startTime  // 经过的时间
const arr = users.filter(u => u.a) // 活跃用户

// 重构后
const elapsedMs = new Date() - startTime
const activeUsers = users.filter(user => user.isActive)

用卫语句替代嵌套(Replace Nested Conditional with Guard Clauses)

javascript
// 重构前
function getPayAmount(employee) {
  if (employee.isSeparated) {
    return { amount: 0 }
  } else {
    if (employee.isRetired) {
      return { amount: employee.pension }
    } else {
      return { amount: employee.salary }
    }
  }
}

// 重构后
function getPayAmount(employee) {
  if (employee.isSeparated) return { amount: 0 }
  if (employee.isRetired) return { amount: employee.pension }
  return { amount: employee.salary }
}

重构的安全网

重构最大的风险是"改着改着就改出 Bug 了"。所以重构的前提是有测试覆盖。每次小步重构后运行测试,确保行为没变。没有测试的代码,先补测试再重构。


3. 代码审查:团队协作中的质量保障

3.1 为什么需要代码审查?

代码审查(Code Review)是团队中最有效的质量保障手段之一。它的价值不仅在于发现 Bug,更在于:

  • 知识共享:团队成员了解彼此的代码,降低"巴士因子"(如果某人被巴士撞了,项目还能继续吗?)
  • 统一风格:通过审查逐步形成团队的编码规范
  • 提前发现设计问题:比 Bug 更难修的是糟糕的架构决策
  • 互相学习:看别人的代码是提升编程能力的捷径

3.2 审查什么?

维度关注点
正确性逻辑是否正确?边界条件是否处理?
可读性命名是否清晰?结构是否易懂?
安全性是否有注入风险?敏感数据是否暴露?
性能是否有明显的性能问题?N+1 查询?
测试是否有对应的测试?覆盖了关键路径吗?

3.3 审查的礼仪

好的代码审查是对代码的讨论,而不是对人的批评

  • 用"我们"而不是"你":"你这里写错了" → "这里我们可以考虑用 guard clause"
  • 提问而不是命令:"改成 const" → "这个变量后面会被重新赋值吗?如果不会,用 const 更安全"
  • 给出理由:不只说"不好",要说"为什么不好"以及"怎样更好"

4. 代码质量度量

4.1 圈复杂度

圈复杂度(Cyclomatic Complexity)衡量代码中独立路径的数量。每个 ifforcase&&|| 都会增加复杂度。

复杂度评价建议
1-10简单容易理解和测试
11-20中等考虑拆分
21-50复杂必须重构
50+不可维护紧急重构

4.2 代码覆盖率

代码覆盖率衡量测试执行了多少比例的代码。常见指标:

  • 行覆盖率:被执行的代码行占总行数的比例
  • 分支覆盖率:被执行的条件分支占总分支的比例

覆盖率的陷阱

80% 的覆盖率不代表代码质量好。覆盖率只能告诉你"哪些代码没被测试到",不能告诉你"测试是否有意义"。一个只断言 expect(true).toBe(true) 的测试可以提高覆盖率,但毫无价值。

4.3 实用工具

工具用途
ESLintJavaScript/TypeScript 静态分析
Prettier代码格式化,统一风格
SonarQube综合代码质量平台
HuskyGit hooks,提交前自动检查

5. AI 助力:用大模型提升代码质量

大模型在代码质量领域已经非常实用,它可以充当你的"24 小时在线的代码审查员"。

5.1 识别代码坏味道

提示词

请审查以下代码,识别其中的代码坏味道(Code Smell),包括但不限于:
过长函数、魔法数字、重复代码、过深嵌套、过长参数列表。
对每个问题给出具体位置、问题描述和改进建议。

[粘贴你的代码]

5.2 自动重构

提示词

请对以下代码进行重构,要求:
1. 不改变外部行为
2. 使用提炼函数、卫语句替代嵌套等手法
3. 改善命名,消除魔法数字
4. 解释每一步重构的理由

[粘贴你的代码]

5.3 模拟 Code Review

提示词

请以资深开发者的视角审查这段代码,从以下维度给出反馈:
- 正确性:逻辑是否有 Bug?边界条件是否处理?
- 可读性:命名是否清晰?结构是否易懂?
- 性能:是否有明显的性能问题?
- 安全性:是否有注入或数据泄露风险?
用"建议"而非"命令"的语气,给出改进方案。

[粘贴你的代码]

AI 使用建议

AI 的重构建议需要你自己验证——跑测试确认行为没变。把 AI 当作"提建议的同事",而不是"无条件信任的权威"。


6. 总结

回顾这一路,我们从识别问题到解决问题,建立了一套完整的代码质量改进体系:

  1. 识别:学会闻到代码坏味道,知道哪里需要改进
  2. 重构:掌握安全的重构手法,在测试保护下小步改进
  3. 协作:通过代码审查,让团队共同守护代码质量
  4. 度量:用客观指标追踪代码健康度

终极思考

代码质量不是一次性的工作,而是持续的习惯。就像保持房间整洁一样——不是等到乱得不行了才大扫除,而是每天随手整理。童子军法则说得好:离开时让代码比你来时更干净一点。


延伸阅读

  • 经典书籍:Martin Fowler《重构:改善既有代码的设计》是这个领域的圣经。
  • 代码整洁之道:Robert C. Martin《Clean Code》提供了大量实用的编码原则。
  • 实践工具:尝试在项目中配置 ESLint + Prettier + Husky,体验自动化代码质量保障。
  • 代码审查:Google 的 Code Review 指南是业界标杆,值得学习。