ARTICLE · 994139
AI 写代码时,领域驱动设计更重要

编者按
围绕AI生成代码加剧的领域失焦问题,文章重提DDD:实现技能贬值,但理解业务与建模仍是关键。核心判断是领域模型不是产物,而是团队通过知识压榨形成的共识,所以不能委托给AI生成。工程取舍上,作者主张先团队设计再让agent实现,可尽量缩短评审循环;用统一语言和限界上下文统一名称,能显著提高提示质量——他让AI省内存却未说清目标是降本,结果每月仅省1美元。读者应关注:开发者需亲自思考,不能外包判断。
来源:threedots.tech[1]
无论你对 AI 编程持什么看法,我们构建软件的方式正在改变。每个人都在问:“什么会依然重要?” 我们有的只是观点,我的看法是:领域驱动设计背后的大多数理念,如今比以往任何时候都更加重要,因为 DDD 从来都不仅限于代码。 当我们越来越多地用智能体写代码时,我们仍然需要理解领域、做好建模,并作为团队一起协作。让我们看看 DDD 还能教给我们什么,以及 AI 无法取代什么。
老规矩,先说明一下:我聊的是那些需要维护数月乃至数年的复杂项目,团队往往在那里挣扎,而 DDD 在那里大放异彩。在练手项目或 CRUD 应用里,没有必要使用高级模式。
自 2003 年未变之事
Eric Evans 在二十多年前出版了《领域驱动设计》,但它至今仍出人意料地新鲜。这本书涵盖了很多主题,但核心信息是:软件工程中最难的部分,是理解问题领域并在代码中将其建模好。 它呼吁工程师不要迷失在技术细节中,而要专注于自己正在解决的问题,因为这部分更难做对。
相反,技术人才忙于构建精巧的框架,试图用技术解决领域问题。学习和建模领域则被留给其他人。软件核心的复杂性必须正面应对。否则,就有被淘汰的风险。
如今我们看到,得益于 AI 工具,实现细节变得不那么重要了。在某种程度上,即使对某门编程语言或某个框架没有深入的知识,你也能高效地产出,切换到另一个技术栈也变得更容易。如果你试过顶级的编程智能体,你大概见过它们能快速生成像样的代码。
但了解高层次的软件概念,对于指导智能体、判断输出是否合理仍然至关重要。最重要的是,领域复杂性(你正在解决的业务问题)依然是最难的部分。好消息是,编码比以往任何时候都更容易,所以我们可以专注于这些复杂问题,而不是迷失在实现细节里。
只不过,现在我们不再痴迷于构建框架(或添加更多微服务,或任何其他酷炫潮流),而是追逐模型基准,优化我们的智能体配置,以更少的精力生成更好的代码。正如 Evans 在 2003 年指出的,我们仍然在试图用技术解决领域问题。有趣的是,这一次 CEO 们也都相信了,还推着我们往这个方向走。
只剩一件小事需要搞清楚:这个东西应该怎么运作。如果有人告诉我们,我们就可以把 token 砸到实现上,然后收工。而如果没人知道,那……也许可以让智能体替我们写计划?
领域模型不是产物
DDD 的基础是模型驱动设计:将领域的运作方式提炼成一个用代码表达的模型。领域模型是一个抽象概念,没有唯一的表示方式。你可以用文档、图表和代码来呈现,但它们都是对领域运作方式的简化。一个准确的模型,来自设计与实现之间的反复切换,以及不断应用你所学到的东西。
DDD 称之为知识提炼(knowledge crunching)。你需要领域专家(了解业务如何运作的人),以及擅长构建软件的工程师。他们一起协作,理解软件应该做什么以及如何实现。(例如,在 Event Storming[2] 工作坊中,开发者和利益相关者用便利贴梳理业务的运作方式。)
搞清楚领域是项团队协作的苦差事,所以让别人代劳很有诱惑力。例如,AI 智能体非常擅长研究和分析,可以访问你的文档、聊天和代码。看起来它们可以替你创建领域模型,然后顺便也实现了。
但这种天真的做法没有抓住重点。建模的价值在于,你(和你的团队)理解了领域是如何运作的。AI 生成的一堆文字并不能帮助你搞清楚业务问题。智能体只会制造一个没人会读的、看起来很厉害的产物。
[3]
领域模型不是产物。
挑战从来不是写一份长文档来说明某个东西可能如何运作。更常见的是,软件项目因为其他问题而失败:
工程师在做错误的事情。 根本没人知道首先要做什么。 团队试图一次构建所有东西(范围蔓延)。
这里有一个我踩过很多次的坑。在工程团队里,我们很容易觉得一切尽在掌握:我们是编程专家,懂各种模式,遵循最佳实践。我们只需要有人(业务方或产品经理)告诉我们该做什么。
但通常,领域专家们也没有清晰的计划。他们知道自己遇到的问题,但还不知道解决方案是什么。不存在一份包含所有需求的完整文档。开发者的第一反应可能是:“你们想好了告诉我,我随时都在。” 换句话说,我们是为了编码这个有趣的部分而来的。(注意,这正是 AI 智能体所自动化的部分。)
另一个极端是,非技术背景的管理者试图提出完整的解决方案,然后交给团队实现。或者,最新的趋势是,有人用 AI 生成完整的特性或产品规格,自己看都不看就往下传。所以计划算是存在,但距离可以投入生产还差得远。(如果有人不这么认为,去看看他们交付的东西到底有多复杂。)
在所有这些场景中,没有人真正清楚自己在做什么。不管代码由谁来写、写得有多快,在这个阶段都没有多少价值。
相比之下,当你们一起建模时,团队理解了问题领域,并对解决方案达成一致。开发者实现它,一旦出现意外情况或开始新一轮迭代,领域专家就可以随时介入。
即使你大部分编码都用 AI 完成,你仍然需要深入了解领域,才能像指导队友一样引导智能体。你如何配置智能体、使用哪个 LLM,都不如对解决方案有一个清晰的思维模型来得重要。如果非要选,我宁愿自己完全不写代码,也不愿跳过对“需要做什么“的思考。
先设计,再写生成代码
写代码向来比读代码容易。现在更甚,因为智能体可以一次性为你生成一个大特性。即使它能正常运行,你也不知道底层到底发生了什么。
我们现在更频繁地卡在[4]代码评审上,但这也不是新问题。它甚至让人感觉很熟悉,就像和一个独狼式开发者合作:他准备好了完整特性,然后丢给你一个巨大的 PR。你对设计一无所知,而且常常存在一些没人考虑过的缺口。评审这样的 PR 非常痛苦。
有一种方法可以避免这种情况,我在很多项目里都看到它奏效。在坐下写代码之前,团队要先设计和讨论解决方案。 首先是领域部分:解决什么问题、它应该如何运作(见上文的知识提炼)。然后是技术层面。实现启动之后,就基本不会有什么意外了。
你不需要在每一个细枝末节上都达成一致,也不需要一份完整的书面规格说明。画出一个大家都了解的高层计划就够了。几张便利贴加一块白板就足够了。
[5]
改变设计的最佳时机,是在任何人写代码之前。
经过这样的设计会议之后,评审 PR 就顺畅多了。如果你不用在 PR 评论里讨论整个设计,就很容易把工作拆成小块。特性上线后也会少很多缺口,因为你们已经就细节达成一致,每个人都清楚自己在构建什么。
跳过设计看起来像是在节省时间。没人喜欢开更多的会。但如果你的团队是从 PR 里才第一次了解这个特性,那么评审、讨论和修复所有问题会花上更长的时间。由于评审通常是异步的,你最终会得到很多轮评论。代码评审应该是对实现正确性的复核,而不是一场关于方案本身是否合理的讨论的开端。
[6]
从一份简短的计划开始,能在之后的评审中帮你省下时间。
先做设计再写代码,也会让你与 AI 智能体协作得更好。 如果你给智能体更多高质量的上下文,它的输出会更准确,编码阶段也会更短。而且你的团队对解决方案了解得越多,评审就越轻松,即使所有代码都是生成的。
通用语言:与智能体说同一种语言
最近,我一直在尝试削减云成本。这对 AI 来说是个完美的任务,因为有一个显而易见的衡量指标。我让智能体找出我们 Go 服务中内存占用最高的地方,然后削减它。
跑了几轮之后,我帮团队省下了很多兆字节的 RAM。但随后我意识到内存很便宜,这些改动每个月只省下了 1 美元。不意外吧?我没有向智能体解释我是在削减成本,只是把内存占用作为目标给了它。
这听起来应该很熟悉,因为团队中人与人之间发生的事情正是如此。那些有助于团队沟通的好习惯,在与 AI 协作时同样适用。(谁能想到软技能还能帮你应付电脑?)
DDD 针对这个问题,从语言使用入手提出了几个理念。
第一个是通用语言[7](Ubiquitous Language):在团队之间和代码中使用同一种语言,让每个人都知道你在说什么。
在很多团队中,混乱的命名是常态。在代码里,工程师使用的技术术语会与公司其他人使用的名称渐行渐远。即使你把文档和决策记录喂给 AI,智能体也需要理解你在其中使用的语言。
在知识提炼过程中,要想清楚领域中的概念如何命名。一开始,每个概念通常会有多个名字。选择一个你已经在使用的名字,或者想出一个更准确的。然后,在文档、口头交流和代码中都使用它。
[8]
Event Storming 工作坊是选定名称的好地方。
命名不能一劳永逸,因为语言会随着项目不断演化。在设计会议和讨论中,你需要持续关注这一点。
与通用语言直接相关的另一个理念是:在更大的系统中,你选择的名称不会在所有领域都通用。DDD 将每一个这样的领域称为限界上下文[9](Bounded Context)。同一个客户,在支持上下文中可以称之为 profile(档案),在电商上下文中可以称之为 user(用户)。你应该在每个上下文内部使用一致的名称,而不是强行在整个项目中统一。
在更大的产品中,当需要在团队之间拆分软件时,这一点尤其重要。如果过于执着于为所有任务创建一个统一模型,就很难划清边界。心中有限界上下文,你就更容易得到一个解耦的系统。
智能体可以访问你的整个代码仓库,它们会查找你给出的名称。它们可能会天真地尝试统一相似的实体,所以你要明确说明:这些实体之所以分开是有原因的。
[10]
同一个领域概念,在不同的上下文中可以使用不同的名称。
如果你使用众所周知的、精确的概念,就更容易表达你的意思。这对你的队友和你的提示词都适用。
一个模糊的提示词可能会让智能体在错误的方案上浪费大量精力:
用户创建后,添加到 CRM 和支持系统
坚持使用精确的名称,会让目标更明确:
用户注册网站后,异步创建:1) CRM 中的客户记录,2) 支持系统中的档案
你需要让智能体了解你领域语言的细节。这也是保持文档更新的又一个理由。靠近代码的 Markdown 文件是一个很好的起点,因为你不需要自定义集成来获取文档。
编者注
原文说 Event Storming 工作坊是选定名称的好地方。Event Storming 是 Alberto Brandolini 在 2013 年提出的一种工作坊形式,官方定义为“用于协作探索复杂业务领域的灵活工作坊格式”。它让业务与技术背景的参与者用便利贴在时间线上排列领域事件,共同梳理流程、暴露不同视角的分歧;在 DDD 语境下,其 Software Design 形式被官方描述为“锤炼通用语言(Ubiquitous Language)”的绝佳场合。命名正是通用语言的核心动作,工作坊中事件、命令、角色等概念会被当面讨论、反复命名,因此比文档或会议更容易收敛出各方接受的名字。
来源:eventstorming.com[11]、avanscoperta.it[12]、leanpub.com[13]
开发者即领域专家
模型和智能体在持续进步,但你不能完全信任它们。它们仍然会犯错,最终需要有人来负责。你怎么知道结果是否正确?
要建立“你知道自己在做什么“的信任,就得在领域中工作。 设计系统并理解其运作方式的人,能够告诉你某件事是否可能出错。
开发者会成为自己工作领域的专家。他们学到写代码之外的技能,对于一个有好奇心的工程师来说,即便是复杂的业务领域,也会随着时间推移变得得心应手。如果你在那个领域需要帮助,你知道该问谁。与一个信得过、不需要极其详细指令就能完成任务的人合作,能省去大量麻烦。
最近有两种关于 AI 的叙事。一种是:现在任何人都能生成一个知名应用的克隆版。起初这很震撼,但从长远来看并没有那么有意思。即使没有 AI,市面上已经有太多半成品应用了。
更有意思的故事是,经验丰富的工程师现在可以构建更复杂的应用。例如,一个团队可以用自研方案取代昂贵的第三方软件。但他们怎么能相信自己的版本能正常工作?
因为他们在这个特定领域工作,已经积累了相应的知识。 他们将自己所知与 AI 结合,如此迅速地构建出自己的版本,甚至比支付许可费还便宜。除此之外,他们还可以添加任何需要的自定义功能。
对于雇主来说,身边有熟悉领域、具备产品思维的工程师是巨大的优势。他们能搞清楚那些晦涩的细节,并引导讨论。把开发者当作“你让我写什么就写什么“的人,这种想法从来都是天真的,如今更是如此。
对于工程师来说,学会与陌生领域打交道,比只专注技术技能更有回报。如果你擅长解决业务问题,而不是只当一个框架的专家,就更容易在利润中心工作。
思考不可外包
DDD 展示了软件远不止写代码。如果你做过复杂系统,我相信你已经见过很多次了。
这个话题让我想起了我的第一份软件工作。有一次我遇到一个棘手的问题,花了几个小时用笔在纸上涂涂画画。最后我终于想明白了,尽管我几乎没碰键盘。那时我刚入职不久,所以我说不出话来:“哇,今天我拿钱是为了思考。”
这在当时感觉很不可思议,但今天感觉更不可思议。那时,如果我说自己花了一整天思考一个问题,我的经理眼睛都不会眨一下。如今,我们更关注产出。为什么不让 AI 替我们思考呢?
[14]
思考,外包了。
首先,我之所以能判断智能体的输出是否合理,唯一的原因是我过去花了大量时间思考这类问题。
有一种观点认为你不再需要这种经验,反正 AI 会替你处理一切。依赖 AI 最终可能会成为新常态,但有一个问题我无法忽视:你需要动脑来解决复杂问题,不思考就无法建立新的思维模型。
我想到的最好的类比是读一本书和读它的摘要。阅读迫使你花几个小时思考这个主题。你在脑海中慢慢构建一个复杂的模型,并把它与你已知的其他事物联系起来。你真正理解了主题,而不是读了几句话,关掉浏览器标签页十秒后就忘了。
目前还没有人知道最佳实践是什么,所以现在正是实验和探索的好时机。就目前而言,我会把老派的问题解决方式与尝试新的编码工作方式结合起来。
#领域驱动设计 #人工智能 #软件工程 #编码代理 #领域建模

1. Copilot 的幻觉
原创
2. Google 软件设计文档
原创
3. “AI 生成的 PR 几乎都是'垃圾'” - Zig 作者采访
原创
4. 驯服 Agent:Superpowers 行为框架
原创引用链接
https://threedots.tech/post/ddd-and-ai-coding/ https://academy.threedots.tech/knowledge/event-storming/ https://threedots.tech/post/ddd-and-ai-coding/domain-model.png https://threedots.tech/post/understanding-code-is-bottleneck https://threedots.tech/post/ddd-and-ai-coding/design-as-team.png https://threedots.tech/post/ddd-and-ai-coding/plan-first.png https://academy.threedots.tech/knowledge/ubiquitous-language/ https://threedots.tech/post/ddd-and-ai-coding/names.jpg https://academy.threedots.tech/knowledge/bounded-context/ https://threedots.tech/post/ddd-and-ai-coding/bounded-contexts.jpg https://www.eventstorming.com/ https://www.avanscoperta.it/en/eventstorming/ https://leanpub.com/introducing_eventstorming https://threedots.tech/post/ddd-and-ai-coding/thinking.png