概述
DRY(Don't Repeat Yourself)的核心原则是:系统中的每一处知识都必须有唯一、明确的表示。
如果你发现自己把同一段逻辑写了两遍,就把它提取出来。重复是等待发生的 bug。
何时使用
- 正在编写与已有代码相似的代码
- 正在复制粘贴并修改
- 正在多个文件中做同样的修改
- 校验逻辑在多个表单中重复
- 相同的计算出现在不同位置
铁律
绝不要重复逻辑。提取并复用。没有例外:
- 不因为“复制更快”
- 不因为“它们略有不同”
- 不因为“我以后再重构”
- 不因为“就几行而已”
检测:复制粘贴异味
如果你正要复制代码并修改它,停下来:
// ❌ 违规:重复的校验
function validateRegistrationEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function validateProfileEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); // 同样的逻辑!
}
// ✅ 正确:单一事实来源
function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// 到处复用
const isValidRegistration = validateEmail(regEmail);
const isValidProfile = validateEmail(profileEmail);
检测:“需要改多处”测试
如果修一个 bug 需要改动多个位置,说明存在重复:
// ❌ 税率计算有 bug,需要改 3 个文件
// cart.ts: const tax = price * 0.08;
// checkout.ts: const tax = price * 0.08;
// invoice.ts: const tax = price * 0.08;
// ✅ 单一事实来源
// tax.ts: export const calculateTax = (price: number) => price * TAX_RATE;
正确模式:提取并参数化
当代码“几乎相同”时,提取公共部分,把差异参数化:
// ❌ 违规:略有差异的相似函数
function formatUserName(user: User): string {
return `${user.firstName} ${user.lastName}`;
}
function formatAdminName(admin: Admin): string {
return `${admin.firstName} ${admin.lastName} (Admin)`;
}
// ✅ 正确:参数化
function formatName(person: { firstName: string; lastName: string }, suffix?: string): string {
const name = `${person.firstName} ${person.lastName}`;
return suffix ? `${name} (${suffix})` : name;
}
抗压协议
-
“复制更快”
- 压力:“我就复制这个改一下”
- 回应:复制会产生两个需要维护的地方,bug 会分叉。
- 行动:先提取共享逻辑,再在两处使用。
-
“它们略有不同”
- 压力:“这些函数几乎一样但不完全一样”
- 回应:“几乎一样”=提取公共部分,把差异参数化。
- 行动:找出共享部分,提取它,把差异变成参数。
-
“就几行而已”
- 压力:“只有 3 行,不值得提取”
- 回应:3 行重复 5 次=15 行要维护,bug 会成倍增加。
- 行动:即使很小的重复也提取,并给它们起好名字。
-
“我以后再重构”
- 压力:“先发布,之后再 DRY”
- 回应:你不会的。重复会扩散。现在 DRY 只需 2 分钟。
- 行动:在提交重复之前先提取。
危险信号——停下并重新考虑
如果注意到以下任何一项,你即将违反 DRY:
- 工作流中使用 Ctrl+C / Ctrl+V
- “这个和那个函数很像”
- 同一正则/校验出现在多处
- 相同的错误处理模式重复出现
- 相同的数据转换逻辑重复
- 常量在多个文件中定义
以上所有情况都意味着:提取到共享位置。
重复的类型
| 类型 | 示例 | 解决方案 |
|---|---|---|
| 代码 | 同一函数体出现两次 | 提取函数 |
| 逻辑 | 同一算法,不同名称 | 提取并参数化 |
| 数据 | 同一常量出现在多个文件 | 集中常量 |
| 结构 | 同一类形状重复 | 提取接口/基类 |
| 知识 | 业务规则出现在多处 | 单一事实来源 |
快速参考
| 症状 | 行动 |
|---|---|
| 复制粘贴代码 | 提取共享函数 |
| 同一校验出现两次 | 创建校验器模块 |
| 同一常量出现在多个文件 | 创建常量文件 |
| 相似函数 | 提取+参数化 |
| 修 bug 需要改多处 | 合并到一处 |
常见借口(全部无效)
| 借口 | 现实 |
|---|---|
| “复制更快” | 维护重复更慢。 |
| “它们略有不同” | 提取公共部分,参数化差异。 |
| “就几行” | 几行 × 多处=大量 bug。 |
| “我以后再重构” | 你不会的。现在就提取。 |
| “上下文不同” | 同一逻辑=同一代码,与上下文无关。 |
| “复制版更易读” | 命名并提取后的函数更易读。 |
底线
一处知识,一处代码。
当你要写相似代码时:停下,找到已有代码,必要时提取,然后复用。重复是维护噩梦的根源。