把 Markdown 放进 /src

Carson Gross 认为 Markdown 正在从文档变成源代码,应该提交进 /src、与它生成的代码放在一起,让代码和测试从 Markdown 派生,而不是从转瞬即逝的 prompt 里来。

中文
复制

TLDR

  • Markdown 正在变成源代码,而不是文档
  • 这样的 Markdown 应该提交进 /src,和它生成的代码放在一起
  • 代码和测试应该从这份 Markdown 派生,而不是从转瞬即逝的 prompt 派生(或者至少,prompt 会话最终应该沉淀成持久化的 Markdown)

引言

为了补贴我在 Montana State University 当教授的薪水,我在外面做咨询。我喜欢咨询,也喜欢写代码、帮忙搭系统,既是为了这件事本身,也是因为它让我的技能保持不过时,让我能给学生讲软件开发的最新思路。

过去几年开发领域最大的变化显然是 agentic coding:用 LLM 生成代码,取代手写。我写过几篇相关的文章:

这篇文章里我想聊一个想法,它在我为那些把 agentic coding 放在首位的公司工作时变得越来越清晰:

Markdown 现在是源代码,不是文档。

这当然不是什么新鲜或特别聪明的想法。

在 Markdown is the new source code 一文中,Hartley Brody 写道:

软件的应用逻辑开始像是用 markdown 定义和编辑的,而 agent 实际生成的代码,某种程度上正在变成一个底层的实现细节。

正如上面那几篇文章所显示的,我对 AI 生成的代码态度矛盾。但我的咨询工作表明,各家组织正在朝这个方向走,而且往往速度惊人。

接下来我想讨论的是:当 Markdown 越来越成为软件系统的真相来源,这意味着什么。

缺失的源代码

有一种思路,上面那段引文就是它的写照:LLM 类似于编译器,把高层规格变成底层实现。按这种看法,我们不需要看 LLM 生成的代码,就像我们不看编译器生成的机器码一样。

正如我在 Code is Cheap(er) 中提到的,我并不完全认同这个类比,原因有几个,但与本文相关的是:编译器工作流会保留原始源代码,而 LLM 工作流通常不会。

如今,LLM 生成的代码往往是开发者构建功能时,通过一连串提示词喂给 agent 产生的。实践中,这意味着生成的代码就是该功能最接近“基准真相”的东西。功能文档可能存放在别处(比如 Linear、Slack 讨论串、wiki 等),但就代码库而言,生成的代码才是事实来源。

我的看法是,在专业的 agentic 编程环境中,我们必须接受一个现实:来自临时提示会话的 LLM 生成代码并不理想,并应开始转向在源码目录中把 Markdown 与生成的代码一起捕获并提交。

Markdown 即源码

Markdown 有许多优良特性,使它类似于传统源代码:

  • 它是纯文本,因此可 diff、可 grep,也能在 pull request 中审阅
  • LLM 能原生读写它
  • 人类无需工具即可阅读和编辑

事实上,它已经在某种程度上充当源码了,比如 AGENTS.md、specs、plans、TASK.md 等等。我们只是还没有把这种源码的捕获标准化。

在 Markdown is the new source code 一文中,Brody 说他工作时会把 Markdown 文件放在 .scratch/research/ 和 .scratch/plan/ 里。我也沿用了类似惯例,为临时性需求创建 /tmp 目录。

我的提议是,把其中一些文件提升到一个新目录,与现有源代码并列:/src/md

这个拟议目录中捕获的 Markdown,会比传统设计文档更底层:

  • 它包含架构决策
  • 它包含源码级决策
  • 它包含底层数据设计决策

它更接近规格说明(尽管并不是),而不是项目经理或设计师按传统方式管理的设计文档。

局部性

我是局部性的拥趸,我认为把 Markdown 放进 /src 有很强的局部性优势:

  • 代码模块现在会包含解释代码意图的 Markdown
  • 不会再有诡异的“远程规格说明”——为什么这样写的逻辑散落在 wiki/Notion/Confluence/Jira 的某处
  • /src 里的 Markdown 可以同时被人和 agent 消费
  • agent 不再需要去别处寻找某个代码库的上下文

那 Linear/Wiki 之类的呢?

系统行为的其他事实来源仍然可以存在。这些来源提供更高层的、或者说“面向流程”的文档:高层设计文档、需要解决流程的 issue,等等。

但系统核心的、当前的、静态的预期行为,会越来越多地直接记录在源码目录里的 Markdown 中。

那测试呢?

我在网上看到很多人说,测试就是新的规格说明(或者说一直都是)。我觉得这话有一定道理。

但测试并不是人机交互的好机制:

  • 它们涉及大量仪式性的东西,往往掩盖了真正在测什么
  • 它们通常比大多数人愿意处理的层次更低,尤其是在理解一个系统的时候
  • 像 Mermaid 图这样的高层解释没法自然地放进去

我认为下面这种分工是合理的:

  • Markdown 放在 /src 里,充当(大致上的)规格说明
  • 测试放在 /test(或随便哪里),以这些 Markdown 为依据,提供自动化的正确性确认

再强调一次,这里的核心想法是:开发者不再是靠 prompt 生成代码和测试,而是在 /src 目录里写 Markdown,代码和测试由它派生出来。

/src/md 里的 Markdown 长什么样

/src/md 里的 Markdown 介于系统的形式化规格说明和高层设计文档之间。

和源码一样,这些 Markdown 也有对应的复杂度预算。要让这些文档保持干净、分解得当、处在合适的抽象层次上,需要有意识地去管理。

开发者应当预期自己会同时接触 Markdown 和由它派生出的代码,因此,在适当的时候让两者保持同步将成为一项重要技能。

例如,开发者常常会对生成的代码做减法式、约束性的修改,而这些改动可能需要回写到 Markdown 中。

我认为,/src/md 中的大部分内容不应由 agent 生成。这个目录主要应由人来撰写和维护。

一个提议的 /src/md 约定

这部分必然是本文最薄弱的地方,因为这是个新想法,我还没有大量使用过。这只是我在边想边说,抛出来供大家讨论。

尽管如此,下面是一个可能的 /src/md 标准:

src/
 md/
 README.md # index of all md, entry point for agents
 TODO.md # a list of general TODOs open for this module
 OVERVIEW.md # a technical overview of this module
 features/FEATURE_1.md # a set of feature-specific documents
 data/DATAMODEL_1.md # descriptions of data models in the module
 api/API_1.md # descriptions of APIs the module provides
 infrastructure/INFRASTRUCTURE_1.md # descriptions of infrastructure used by the module

其中 features、data、api 和 infrastructure 目录都是可选的;思路是按不同维度切分,以便在 /src/md 文件夹中直接给出对模块行为扎实、可用的描述。

结论

当代码的生成成本越来越低,真正有价值的只剩下代码背后的意图:它做什么、为什么这么做、以及它绝不能做什么。

今天,这种意图往往散落在一次性的提示会话里,或者零散地分布在 wiki、工单和 Slack 讨论串中。

我认为,出于局部性的考虑,我们应该考虑用 Markdown 记录这些意图,并把它和它生成的代码一起提交进 /src,让人和 agent 都能找到。

我还不知道 /src/md 这类东西的正确结构应该是什么样,而且随着我(和其他人)积累更多经验,我的想法大概也会变。

但我相当确信,Markdown 正在变成源代码,而我们也应该越来越把它当作源代码来对待。

(尽管,是的,LLM 并不是编译器 :))

来源: htmx← 返回首页