AI 时代的编程原则:DRY

发布: (2026年2月2日 GMT+8 10:54)
14 min read
原文: Dev.to

Source: Dev.to

这是系列思考实验的第一篇文章,我在其中通过 AI 辅助开发的视角重新审视编程原则。显然,我们正处于范式转变之中,但没有人确定它会走向何方。因此,我并不试图预测未来;而是想在强假设情景下对假设进行压力测试,看看哪些能够站得住脚。在整个系列中,我们的判断基于更可能的预测——AI 将作为工具使用,而不是作为有感知的同事或乌托邦式的奇点之神。

DRY(Don’t Repeat Yourself)

DRY 来源于《The Pragmatic Programmer》(Hunt & Thomas,1999)。在实际使用中,人们把它当作一种 “提取方法” 的模式:一旦出现代码重复,就应该把它提取到一个公共位置以便复用。

很多人会把它与 Uncle Bob 在单一职责原则(Single Responsibility Principle)中的 single responsibility 视角相联系,而细读《Clean Architecture》的人会发现其中的矛盾;不过我们会在后续文章中讨论这个问题。

DRY 的原始定义比大多数人想象的更宽泛:

“系统中每一条知识必须有唯一、明确、权威的表示。”

为了本文的阐述,需要强调的是,这一原则的核心是 知识重复,而不是 代码重复。代码重复是知识重复的结果,而不是其原因。

为什么 DRY 很重要

DRY 解决了一个特定的问题:人类在追踪重复项时不可靠

你在三个服务中都有相同的税务计算。需求变更后,你更新了其中两个。第三个仍然保持错误,直到四个月后有客户投诉才被发现。

抽象层面的做法是为了解决开发者会忘记复制位置的事实。

换句话说,DRY 是一种 上下文管理策略。人类开发者的思维上下文是有限的:你能记住的内容、屏幕上显示的内容、最近审阅的内容。DRY 通过保证每条知识只存在于唯一位置,减少了你需要保持的上下文量。如果没有复制,就不需要记住复制在哪里。

这种框架很重要,因为它重新定义了我们要回答的问题。

问题不是“重复是否不好?”(答案是肯定的)。问题是:“维护代码的实体是否需要 DRY 来管理其上下文?” 这完全取决于该实体拥有多少上下文。

当 DRY 出错时

在我们把 AI 引入之前,DRY 在被过度激进地应用时已经有众所周知的失效模式。这值得探讨,因为它表明即使对人类来说,这一原则也有其局限性。

示例:电子邮件验证

// Service A
public bool ValidateEmail(string email)
{
    return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}

// Service B – identical
public bool ValidateEmail(string email)
{
    return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}

一次 DRY 重构将其提取到共享库中:

// SharedLib.Email
public static bool ValidateEmail(string email)
{
    return Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$");
}

六个月后:

  • Service A 需要接受加号地址(user+tag@domain.com)。
  • Service B 明确 接受加号地址,因为它是一个反欺诈流水线的一部分,加号地址会被视为红旗。

现在你必须在参数膨胀、策略模式,或放弃并再次复制之间做选择。共享库为你带来了六个月的一致性,随后却变成了 耦合负担

这就是看起来相同的代码被当作相同知识来处理时会发生的情况。两个服务之所以拥有相同的代码是偶然的,而不是因为它们共享同一业务规则。有些人称之为“错误抽象”或“偶然重复”,在微服务架构中,由于服务以不同速度演进,这种情况比人们承认的更常见。

AI‑Assisted Duplicate Management

假设你的开发环境配备了 AI 工具,能够为代码库构建 语义索引,不仅映射语法上相同的代码,还能识别功能上等价的代码。当你修改其中一个实例时,它会找出所有相关实例并生成 上下文适配的差异,为每个受影响的服务打开 PR,且更改会根据该服务的特定约束进行调整。

在这种情况下,电子邮件示例的处理方式会有所不同。AI 能识别出所有三个验证器,明白它们在 + 地址 处理上有意不同,但共享相同的域名验证逻辑,并生成三个不同的补丁,更新域名检查 而不影响 + 地址的行为。每个服务保留自己的验证器。没有共享库。没有版本耦合。没有部署依赖。但在应该保持一致的部分实现了一致性。

AI 是否让 DRY 变得不那么必要?

乍一看,这似乎让 DRY 变得不那么必要。如果 AI 能在代码库中追踪并同步重复代码,为什么还要提取共享抽象?让每个服务各自拥有一份副本,由工具负责保持一致性。你会得到:

  • 本地可读性
  • 扁平的依赖图
  • 独立部署
  • 在 CI 时由 AI 验证的一致性

听起来很有吸引力。但这个论点存在结构性问题。

正是同样的 AI 能力在实现重复追踪的同时也 加速了代码生成。如果 AI 能够足够了解你的代码库以同步重复,它同样可以更快地生成新功能。开发者在更短的时间内交付更多代码。原本没人会手动编写的样板代码因为边际成本几乎为零而被生成。代码库因此膨胀。

而这里的论证出现了循环:随着代码库的增长,最终会 超出 AI 的上下文窗口。原本用于追踪重复的工具再也无法一次性看到所有重复。

这并非理论上的担忧。当前 LLM 的上下文窗口只有数万甚至数十万 token。一个大型微服务架构可能拥有数百万的…

(文章将在下一篇继续。)

上下文、DRY 与 AI

代码库的增长和 AI 上下文窗口如何重塑“不要重复自己”原则。

1. 核心张力

“即使有 RAG、嵌入和语义搜索,AI 同时能够推理的内容仍然有硬性上限。我们用来扩展有效上下文的每一种技术,都伴随着精度和可靠性的权衡。”

令人不安的认识是,反对 DRY 的论点本身是自我否定的

  • 那些削弱 DRY 的力量(更好的 AI、更大的上下文)同时也创造了加强它的条件(更多代码、更大的代码库、上下文溢出)。

2. 放大视角:上下文 vs. 代码库

问题: 上下文窗口的增长速度是否快于代码库的增长速度?

历史趋势很明确:

  • 代码库已经增长了数十年。
  • 每一次生产力提升——IDE、框架、包管理器、CI/CD,以及现在的 AI 辅助开发——都导致代码增多,而不是减少
  • 行业从未因生产力提升而写更少的代码;我们写更多、更快,以实现更宏大的目标。

3. 与计算资源的类比

资源过去趋势当前类比
RAM 与应用内存RAM 增长 → 应用使用更多内存上下文窗口增长 → 代码库消耗更多上下文
CPU 速度与软件复杂度更快的 CPU → 更复杂的软件更大的上下文窗口 → 更大的代码库
网络带宽与数据量带宽 ↑ → 数据量 ↑上下文 ↑ → 需求 ↑

帕金森定律在软件领域的体现:代码会扩展以填满可供处理的上下文空间。

如果这一模式成立(且没有强有力的理由相信它不会继续),AI 将始终在其能够完全理解的边界附近运行。它在发现和追踪重复项方面会明显优于人类,但它并没有全知的奢侈。DRY 作为在有限上下文内保持代码库可管理的策略,仍然是相关的——无论上下文属于人还是机器。

4. 有哪些变化?

  1. 阈值移动。

    • AI 能追踪的重复项比人类多,因此重复变得危险的临界点向外迁移。
    • 小的实用重复(例如字符串格式化、基本校验)如果 AI 工具能够验证其一致性,可能就不值得抽取。
  2. 验证变为可能。

    • 即使你选择重复,AI 也可以告诉你:

      “这五个方法实现了相同的业务规则,其中两个已经出现漂移。”

    • 这使得基于信息的决策成为可能,而不是通过生产故障才发现不一致。
  3. 原则的转变。

    • 从“绝不重复” → “有意识地重复,并保持可见性”。
  4. 知识层面 vs. 代码层面的重复。

    • Hunt 与 Thomas 在最初的阐述中区分了两者,但业界大多忽视了这一点。
    • AI 工具现在可以在知识层面强制 DRY,同时容忍代码层面的重复——这或许正是该原则一直想表达的含义。

5. 实践指南

当你准备抽取共享方法或创建共享库时,问自己两个问题:

  1. 业务概念问题

    我之所以抽取,是因为这些东西真正代表相同的业务概念,还是因为代码现在看起来相似?

  2. 上下文管理问题

    我之所以抽取,是因为抽象真的让设计更好,还是因为我需要一种机制在有限的上下文中保持同步?

  • 第一个问题始终是正确的提问。
  • 第二个问题是 AI 改变计算方式的地方——逐步出现,并且仅在工具实际可见的范围内起作用。

6. 结论

就目前而言,DRY 仍然是务实的默认选择——并不是因为它是一条不可动摇的铁律,而是因为在有限的上下文(无论是人类还是机器)中,它仍然是保持代码库可维护性的最佳实践。

cred 原则,但因为 上下文,无论是人类的还是人工的,始终是有限的。只要它是有限的,我们就需要在其限制范围内工作的策略。AI 改变了复制的 阈值可见性,但并未消除对深思熟虑的抽象的需求。

Back to Blog

相关文章

阅读更多 »

Intent 和 Spec

什么是 intent?什么是 spec?Intent 是系统必须具备的目的表达,是它必须满足的需求,是它必须实现的目标。它不…

软件质量的视角

软件质量的不同视角 软件质量——或任何产品的质量——可以从多个视角进行审视,因为不同的利益相关者带来……