pusidun
← All posts

生成式软件工程第三讲笔记:软件仓库管理

这篇笔记对应蒋炎岩《生成式软件工程》第三讲,B 站视频《软件仓库管理》官方讲义使用的标题是《版本管理》。

前两讲讨论如何理解和使用 Agent;这一讲开始处理项目长期存在之后的问题:怎样保存一个可靠状态,怎样解释一次变化,怎样汇合不同人的工作,以及怎样避免更快的代码生产把项目变成难以理解的混合物。

本文由 AI 辅助整理,综合视频音轨的本地自动转录、官方讲义与 100 帧课件抽样,按概念、案例和工作流程重组。实际演示、工具行为与官方讲义中的延伸讨论分别说明,最后一节补充个人实践思考。

一、模型越来越强,为什么仍然要学基础概念

开场延续了前两讲的讨论。讲者对当时模型的使用感受是,幻觉和设计质量有所改善,但面对开放的设计问题,仍然需要人反复评价与反馈。这属于授课时的个人观察,不应据此得出模型之间的通用排名。

一个重要原因是,需求并不总能在开始时完整写清楚。人说想要一个好用的界面,可能只有看到第一版以后,才能指出布局、交互和信息密度哪里不对。实现本身也在帮助需求提出者认识自己的意图。

因此,Intent、Spec、Impl 之间仍然存在大量选择。模型可以迅速探索这些选择,人也需要有足够的概念去判断:它提出的是什么结构,代价在哪里,哪个问题值得现在追问,哪个决定可以留待实验。

口头讲解还谈到空间理解与 3D 打印。讲者用团队此前需要反复调整、后来模型能够更直接完成的设计说明能力变化带来的冲击,并以汽车作类比:早期驾驶者经常需要了解维修原理,工具成熟后,使用者未必还要掌握全部内部细节。这个类比提出的是学习边界会怎样变化的问题;讲者对当下的回答仍然是,正确的基础概念有助于提出更好的设计,未来如何学习则没有定论。

这一讲给出的学习方向,是理解真实系统的构成与行为。工具可以替我们执行命令,但如果不知道命令会改变什么,就很难判断一次操作是否合理,也很难在结果异常时保留退路。

先在脑子里实现一遍

讲义介绍了一个值得长期练习的方法:virtually implement,即阅读一个方案前,先尝试在脑中构造自己的方案。

读论文时,先想作者的问题需要什么数据结构,依赖什么假设,在哪些输入下可能失败,再看实际方法。读代码时,尝试预测接下来的步骤,留意它为什么增加某个检查。看仓库时,也可以问:如果是我组织项目,会留下哪些文件,又会删除哪些文件?

比较之后,差异就成了学习入口:是自己没有看到某个约束,还是对方使用了一个更合适的抽象?如果只是快速浏览大量项目,许多优秀设计会经过眼前,却没有进入自己的知识结构。

Git 很适合这样学。可以先预测一条命令的后果,再观察文件、状态和历史怎样变化。能预测行为,比只知道工具名称更接近真正掌握它。

二、一个项目目录,还缺少哪些管理能力

软件项目首先表现为一个目录。里面的文件可以承担不同职责:需求描述意图,接口说明与测试表达约束,源代码实现行为,构建脚本把它们变成可运行或可交付的结果。

这里还有一个课堂中的小提醒:本机 localhost 上能打开的网页,并不意味着其他人已经能访问。想把产品交给外部使用者,就需要把部署与访问要求纳入意图和规格。否则 Agent 按开发者的常见习惯启动本地服务,也可能是在合理地完成它所理解的任务。

但目录只展示当前状态。昨天能够运行的版本,今天改坏之后还找得到吗?一个功能做到一半,临时需要修复别的问题,怎样保存未完成工作?两个人从同一个版本出发修改,怎样把结果放回一起?

讲义用优盘丢失作业代码、打包发邮件交作业等经历说明,保存文件和管理项目历史并不是同一回事。存储设备没有损坏,也不代表我们知道每个状态从哪里来、哪几份文件应该配套使用。

版本管理需要记录的,既包括项目曾经是什么样子,也包括状态之间的关系。课程可以通过 git clone 分发初始项目、通过 git pull 提供更新,再用项目定义的入口提交作业。这里的 make submit 属于课程 Makefile 提供的流程,不能把它误认为 Git 内置命令。

三、从手工复制目录到 Git 快照

最朴素的版本管理,是不断复制整个目录,保存为第一版、第二版、最终版。这确实能够留下旧状态,但版本多起来以后,人还要自己记住先后关系、文件之间的配套关系,以及哪一版可以交付。

版本工具逐步把这些关系显式记录下来。讲义回顾了 RCS、CVS、SVN 与 Git 等工具,强调它们来自真实协作需求。Git 与 Linux 内核开发关系密切,速度、大规模协作和分支支持都不是凭空提出的功能。

快照保存的是项目状态

理解 Git 的起点,是把一次版本看作被跟踪文件的快照。它描述文件内容和目录位置。源代码、配置、接口经常需要一起变化;只拿回旧源码而留下新配置,可能拼成一个从未验证过的组合。

一次 commit 把快照与父提交、作者、说明等信息关联起来。父提交记录历史关系,因此多个提交能够构成一张有方向的图。删除一个已经提交过的文件,只会让新状态里不再出现它;旧提交仍然可能保存着它。

编辑器保存文件,与 Git 提交文件,是两种不同的保存:前者更新工作区,后者把经过选择的项目状态写入历史。

完整快照不等于每次复制全部字节

Git 用对象表示内容与结构,未改变的内容可以复用。逻辑上可以把提交理解为一个完整状态,物理存储上却不需要为每个提交复制整套目录。

课堂用目录树画出了持久化数据结构的思路:一个叶子文件改变以后,需要形成新的文件内容,以及从该位置到根节点的一条新路径;其他未改变的子树可以继续共享。这样,一个新根就能代表新快照,旧根仍然保留旧状态。这是理解逻辑结构的示意,不意味着实际存储只有这一种编码方式。

这一讲介绍的三个重要对象可以这样对应:

对象 表达的内容
blob 文件内容,本身不负责保存文件名
tree 目录中的名称、对象与目录结构
commit 根目录树、父提交与提交信息

这些对象让“某个版本”从一份人工命名的目录,变成可以追踪关系、比较内容和共享历史的结构。GitHub、GitLab 可以托管远端仓库,但本地创建和记录版本,并不以连接托管平台为前提。

四、工作区、暂存区与提交:先分清三个状态

git init 在目录里建立仓库,git clone 则从已有仓库创建本地副本。开始提交之前,需要分清三层状态:

  • 工作区:当前正在编辑的文件。
  • 暂存区,也称 Index:下一次提交准备采用的内容。
  • 提交历史:已经记录下来的快照与关系。

git add 把执行时的文件内容放进暂存区;普通的 git commit 依据暂存内容创建提交。它不是简单地把提交瞬间看到的所有文件自动打包。

因此,下面这个操作顺序很适合实际验证:

  1. 修改 hello.c
  2. 执行 git add hello.c
  3. 再次修改 hello.c
  4. 执行普通的 git commit

提交采用的是第二步暂存下来的版本。第三步新增的变化仍留在工作区,除非再次暂存,否则不会因为文件名相同而自动进入提交。

观察时,可以把命令与比较对象对应起来:

命令 主要观察什么
git status 哪些路径已暂存、未暂存、未跟踪或存在冲突
git diff 工作区相对于暂存区的变化
git diff --cached 暂存区相对于当前提交的变化

暂存区的价值,是让一次提交有可以主动组织的边界。工作区里可能混着多个意图,我们可以先形成一个完整、清楚的变化,再处理其他部分。

视频中的小实验:提交成功以后,再提交会怎样

课堂用 hello.c 实际执行了暂存和提交。工作区已经干净时,再运行普通提交不会凭空产生新版本;随后又使用 --allow-empty 创建了一个不改变文件快照的提交。这个对照把文件内容的变化与历史中的提交对象区分开来。

演示也故意使用信息不足的提交说明,为后面的审查留下问题。空提交是 Git 支持的能力,在一些工作流里有明确用途;不能因为这次教学样例不需要它,就把空提交一概当成错误。

五、分支、合并、冲突与 rebase

分支是引用,历史可以分叉

分支名可以理解为指向某个提交的可移动引用。在通常的分支工作方式下,HEAD 指示当前分支;创建新提交以后,该分支向前移动。另一个分支可以停在原处,也可以继续走出另一条历史。

这让实验和主线能够同时存在。不同人或不同 Agent 可以从共同起点工作,再决定怎样整合结果,而不必在同一个文件状态里同时覆盖彼此的编辑。

merge 如何汇合两条路线

git merge 结合共同祖先以及双方变化,尝试形成合并后的状态。

如果当前分支的提交是待合并分支的祖先,可以把当前分支引用推进到后者,这称为 fast-forward。反过来,如果当前分支已经包含对方的历史,通常无需再合并。如果双方已经各自前进,常见结果是增加一个具有两个父提交的合并提交,把汇合关系记录在历史里。

当 Git 无法自动决定文本内容时,工作区文件可能出现以下标记:

<<<<<<<
一方的内容
=======
另一方的内容
>>>>>>>

真实标记通常还带有分支或版本信息。处理冲突需要阅读上下文,把文件改成最终应该存在的内容,删除冲突标记,再通过 git add 标记解决并继续合并。不是机械保留左边或右边,也不是只让标记消失就算完成。

二进制冲突、修改与删除之间的冲突,未必长成这种形式。因此还要结合 git status 判断当前状态,并验证合并结果。文本能够自动合并,也不意味着合并后的行为一定正确。

视频中的实际演示从共同代码分出 A、B 两条路线,分别改变 main 的声明。随后在 B 上合并 A,Git 报告冲突,文件中出现双方内容与标记,状态显示 both modified,仓库也留下 MERGE_HEAD。这时合并停在等待解决的中间状态,还没有成功形成最终合并提交。把它保留下来观察,能够直接看见工具怎样表示一项尚未完成的操作。

之后,课堂继续解决冲突,可视化中出现了具有两个父提交的新 commit。演示还观察到 Index 的冲突条目保留了共同基线、本方和对方版本,即 base、ours、theirs;它们不等于工作区里必然出现三个额外临时文件。这个过程把三个阶段连起来:无法自动决定时保留现场,修改工作区形成结果,再用提交记录两条历史的汇合。

rebase 改变的是提交的起点

git rebase 把一串提交代表的变化,重新应用到另一个起点上。例如基于旧主线开发时,主线已经出现新提交,可以把自己的变化重放到新主线后面。

重放通常生成新的提交标识,也可能产生冲突。它可以帮助整理历史,但如果其他人已经基于原来的提交继续开发,改写这些提交就会增加协调成本。

理解这些操作以后,可以在执行前问四个具体问题:工作区会怎样变化,下一次提交包含什么,哪个引用会移动,历史会增加或改写什么关系。这样操作不再只是记忆命令字符串。

六、仓库里应该保存什么

哪怕只有 hello.c 和编译得到的 hello,也已经需要工程判断。如果两者都提交,下一次修改源码却忘了重新编译,仓库里就可能出现不一致。只提交源码,也要让别人知道如何构建。

“当前目录里存在的文件”与“团队需要共同维护的项目状态”,范围并不相同。

讲义建议初学者让 Agent 在进行 Git 操作之前,逐个说明文件的作用、是否应该提交、理由是什么。这样,一次真实变更就能够成为学习仓库规范的机会。

常见文件的判断,可以从职责出发:

文件类型 通常怎样处理 原因
源码、共享配置、构建说明 纳入版本管理 构成需要协作维护的项目定义
编译产物、普通运行日志 通常忽略 可以重建,或只反映某次运行
带绝对路径的个人配置 通常忽略或改为模板 不一定适用于他人的环境
密钥和服务凭据 不进入公开仓库 共享代码不需要共享访问权限
.DS_Store 等系统临时文件 忽略 不属于项目行为或构建输入
依赖锁文件 按生态和项目约定保留 虽由工具生成,却可能决定可重复安装的版本

这些判断不是一张根据扩展名机械删除文件的清单。有些项目会有意保存生成的头文件或发布产物,团队共享的编辑器设置也可能应该保留。真正需要回答的是:为什么共同维护它,怎样避免它与其他状态不一致。

故意做错,再检查 Agent 能否解释

课堂把 hello.c 编译得到的二进制也提交进仓库,再让 AI 审查。审查指出了构建产物、缺少信息的提交说明及多余历史等问题,随后整理为添加源码与忽略产物等更清楚的变化。这是故意制造问题的教学过程,重点是每一步为什么值得修正,而不只是接受 AI 的评价。

另一个演示让工作区里的 hello.c 暂时无法编译,并观察 git blame 中的 Not Committed Yet。工作区当前存在错误,与某个已提交版本已经损坏,是不同状态。先知道变化停在哪一层,才有条件决定如何保留、修复或撤销它。

.gitignore 不会抹除历史

.gitignore 把忽略规则写进项目,但它不会自动停止跟踪已进入版本管理的文件,更不会删除过去提交中的内容。

这也说明,错误地提交敏感凭据后,仅从最新文件里删掉它,不能使旧版本和已经传播的副本消失。项目隐私必须在提交之前就纳入判断。版本管理的优势恰恰是保存过去,因此不能把“当前看不见”当成“从未公开”。

七、一个规则名的大写,也能训练设计判断

讲义用下面两个规则标识讨论微小的设计选择:

[TYPO-CHECK] Docs should be typo-free
[typo-check] Docs should be typo-free

规则含义一样,差别在名称如何呈现。大写让固定标识与后面的自然语言说明更容易区分,也方便在讨论中明确引用某条规则。

但是,如果项目已经统一使用小写,或者解析工具要求小写,那么保持一致可能比局部醒目更重要。这里没有得出大写永远更好的结论,而是把一个说不清的直觉拆成可讨论的因素:视觉区分、引用方式、上下文与一致性。

类似的问题经常隐藏在优秀项目中。愿意追问一个细节为何存在,就能逐步理解设计者做过的权衡;不必等到设计大型架构时才开始训练设计能力。

口述中的另一个例子是文档排版与提交语言:中英文之间是否留空格,可以遵循项目约定;令人难以维护的是同一份文档忽有忽无。课堂也指出 Agent 的提交说明会从英文切换到中文,说明它知道常见格式,却不一定主动维持整个项目的一致性。已有规则需要被明确保留,而不是每次生成时重新选择。

八、gitv:从真实仓库看见 Git 在做什么

这一讲提出制作 gitv 的需求:用 Node.js 接收真实仓库路径,启动 HTTP 服务,在浏览器中展示 Git 的主要概念与变化。

它与一幅抽象的分支示意图不同,目标是从磁盘上的真实数据出发,建立原始字节、对象与历史之间的联系。

根据讲义,工具需要处理的内容包括:

  • 从文件系统中的数据解析 Git 对象。
  • 展示 blob、tree、commit 及其引用关系。
  • 支持展开文件内容与比较变化。
  • 对大小不同的文件提供合理显示方式。
  • 持续监听仓库状态,在变化发生后更新画面。
  • 让布局可以调整,用动画帮助观察对象和引用的移动。

浅色背景、清楚的布局和顺畅交互,都服务于教学目标:让使用者把注意力放在变化本身,而不是寻找信息在哪里。

怎样用可视化学会暂存与提交

一个有价值的练习,是先做预测,再执行操作,再找证据:

  1. 修改文件并执行 add,预测哪些内容被保存。
  2. 继续编辑但不暂存,观察哪些状态没有跟着改变。
  3. 创建提交,找出新提交、对应目录树与移动的分支引用。
  4. 再创建分支或合并,比较图中新增的关系。

可视化降低了观察成本,但观看动画本身不自动产生理解。预测错误后去解释原因,才会逐步建立可以外推的概念。

实际演示从压缩的原始对象字节展开到内容,再查看 tree 中的名称、文件模式和对象引用。可执行文件与普通源码的模式不同,blob 内容也可以是二进制。即使可视化布局还不够完善,这种从存储数据走到抽象概念的过程,也比只看一个标签更容易建立对应关系。

课堂还把生成工具与 Learn Git Branching 等现成工具,以及生成的编程环境与 61A Code 放在一起比较。比较应落在具体使用体验:关键状态是否可见,交互是否顺手,边界情况是否处理妥当,使用者能否因此理解问题。

其中有一个很具体的边界:课堂在 Learn Git Branching 中输入 git add,工具提示这个模拟环境不包含暂存区概念。它可以很好地解释分支关系,却不是完整 Git 仓库的替代品。这也解释了为什么 gitv 的需求强调真实仓库和原始对象:不同教学工具选择展示的抽象层不同,使用时应知道哪些细节被省略了。

一次生成结果的好坏,适合用于改进这个工具,不能直接推广为模型能力的普遍结论。

九、提交说明与原子变更

update 可以作为提交说明,却几乎没有解释价值。几个月后重新看历史,人需要知道问题是什么、行为怎样改变,以及为何这样改。

讲义用 Conventional Commits 风格举例:

fix(parser): accept repeated spaces between arguments

其中类型标识修改性质,范围定位相关部分,后面的文字说明行为变化。项目可以选择其他格式;这个博客也有自己的中文提交约定。重要的是让表达稳定、信息充分,而不是为了格式统一丢掉内容。

标题之外,正文可以解释旧行为、触发条件以及需要保留的边界。例如命令解析器应允许参数之间存在多个空格,但不能随便删掉引号里的空格。diff 告诉人修改了哪些代码,提交说明补上动机与约束。

原子不是“只改一行”

原子变更围绕一个明确意图组织,可以被解释、验证,并在需要时单独撤销。

修复错误及其回归测试,可能分布在多个文件,仍然可以构成一个完整变化;与此同时,修复错误、升级依赖、全库格式化混在一个提交里,即使提交数量很少,也很难理解。

有边界的提交让审查者能够逐步检查,也给失败定位和回退提供了更可靠的单位。最好让整理后的每一步都处于可以构建、可以验证的状态,而不是只有最后一个提交勉强能运行。

及时保存与整理历史,可以兼顾

探索时需要及时留下检查点,不能为了将来的历史看起来漂亮,让所有工作长期停留在未提交的工作区。交付之前,再按照项目约定组织清楚的变化。

遇到误操作,先保护现场,再考虑通过 reflog 等记录寻找原有引用。远端只保存已经推送过去的内容,并不会自动替没有提交的编辑做备份。

协作还需要约定谁负责集成、哪些历史允许整理、如何发布,以及怎样把修复送到维护中的其他版本。hooks 可以在开发操作附近提供反馈,CI 可以在集成时检查构建和测试;但格式检查通过,不代表提交说明有内容,本地测试通过,也不代表与新主线组合之后仍然正确。

十、官方讲义的案例补充:SWE-bench 与一行修复

官方讲义还加入了 SWE-bench 和 Astropy 的案例。视频音轨中未发现这段案例讲解,以下按讲义作为延伸阅读整理,不把它记为课堂现场演示。

经典 SWE-bench 从真实开源项目的问题和修复构建任务。模型接手一个已有仓库以及 issue 描述,在既有结构与约束下产出补丁。

它要求的不只是写出一段独立程序,而是理解现象、复现问题、找到相关实现和项目规范,再修改并验证。

评测里有两类直观的要求:

  • FAIL_TO_PASS:与目标问题相关的失败测试,在修复后应通过。
  • PASS_TO_PASS:原本正常的相关测试,应继续通过。

它们分别守住“问题被解决”和“已有能力没有退化”。不过,测试验收仍然只覆盖检查到的行为,真实工程还有讨论、审查、发布与使用者反馈。

Astropy 的嵌套模型依赖矩阵

官方讲义使用 Astropy 的一个真实问题说明这件事。其建模工具通过布尔矩阵描述输出对输入的依赖:矩阵中的某个位置为真,表示对应输出依赖对应输入。

两个相互独立的一维模型并排组合时,各自输出只依赖自己的输入,对应区域应呈现对角线关系。出现问题的情况是,先把它们组成一个子模型,再把这个子模型与其他模型组合,内部本来独立的关系却变成了全部相互依赖。

也就是说,嵌套结构改变了表示方式,却不应该改变计算之间真实的依赖关系。这个不变量被破坏了。

根因位于矩阵拼接过程:右侧已经是一个计算好的子模型矩阵,原实现却把相应区域整体填为 1,抹掉了内部结构。修复将填入内容改为 right,保留原来的依赖矩阵。

最终代码差异很小,但要找到它,需要理解模型组合、矩阵含义与嵌套关系。实际修复还增加了不同嵌套形式的测试与更新记录,再通过 PR 合入项目。

因此,“改一行代码”只描述最终补丁的大小,不能描述整个工程工作的难度。真正的工作包含确定问题、找到抽象层次、证明修复没有破坏其他情况,并把变化交给使用者。

Git 与协作平台各管一部分

Git 管理版本,issue、PR 或 MR、审查意见和 CI 状态,则由协作平台组织。GitHub 的 gh、GitLab 的 glab 等命令行工具,可以让人和 Agent 把两边接起来。

完整过程可能是:读取 issue,建立可复现的本地状态,修改和测试,形成提交并推送,创建 PR 或 MR,检查自动化结果,回应审查并继续修改,最后按项目规则集成。

命令行让流程可以被程序操作,但项目是否接受变化,仍然取决于验证、沟通和协作约定。

十一、历史是解释工具,也是调试工具

把项目理解成由原子变更推进的快照之后,历史就不只是一份存档,还可以帮助定位问题。

工具或操作 与历史质量的关系
git bisect 在已知好坏版本间逐步测试中间提交,缩小问题引入范围;中间状态可运行会使它更有用
git revert 用新提交撤销某次变化;变化越完整、越有边界,越容易判断撤销的后果
git cherry-pick 把选定提交的变化应用到其他位置;隐含依赖越少,迁移越容易
git blame 追踪代码行关联的提交,再结合说明理解来历

一些项目要求 linear history,即把主线历史组织得更直接。这可以方便阅读、追踪和移植变化,但线性只是形式。把一串含义不明、各自无法运行的提交排成直线,仍然不是一份好历史。

当多个目标混在一起时,失败很难定位。对人如此,对当前 Agent 也如此。给它一个边界清楚的目标,保留可检验状态,再及时反馈偏差,仍然是有效的工程方法。

十二、项目落在 Agent 能力范围内以后,怎样组织并行工作

视频使用 Pico Playground 等项目说明一种个人实践:先让架构讨论收敛,再并行实现。

先看真实教学需求

这个编程环境面向小学三四年级学生。讲者希望孩子先感到 Python 有趣,而不是从枯燥的输入输出形式开始。现场展示了 Unicode 字符与数字的转换、可前后回放的汉诺塔、循环驱动的方块世界,以及插入排序等程序可视化。

这些例子不只是给代码旁边配一幅动画。进度变化需要与程序执行、编辑器位置和场景保持对应,拖动时要顺畅,坐标轴也要符合解释任务的需要。讲者提到自己曾专门纠正坐标设计,说明验收关注的是能否帮助儿童理解程序。

同一框架还需要支持网页应用与桌面形态。需求涉及多个部件的共同变化,因此能否管理好接口与状态,会直接影响后续增加教学内容的难度。

架构阶段先集中讨论

先与能力合适的模型反复讨论接口、限制条件和整体结构,直到关键前提明确,再把结果写成文档、规则和示例。测试要求也应在这个阶段成为可理解的约定。

口述进一步解释了具体做法:尽量把部件接口设计成纯函数,用统一的执行图组织程序行为,把必须共享的状态集中注册。光标、播放位置等少量全局状态有清楚的归属,其他部件通过接口连接,减少到处读写隐含状态的情况。

给实现 Agent 的规则也围绕这个边界:理解其他模块时先依赖接口,把实现当作黑盒,避免为了做一个局部修改通读全部内部代码。讲者认为,这能够减少上下文消耗,并让多个部件的并行工作更可控。这是以接口足够清楚为前提的实践,不能据此推成任何情况下都不应该阅读依赖实现。

操作系统、编译原理、数据库等基础知识,在这里仍然能够提供判断材料。它们积累了如何隔离变化、表达不变量、协调并发和划分职责的经验。有这些概念,才能更具体地指出模型的设计哪里值得保留、哪里需要修改。

实现阶段有限并行

讲者介绍的实践规模是不超过三个终端,通过 AGENTS.md 说明协作规则,再由人安排相对独立的工作。这是个人选择,不是 Agent 并发能力的技术上限。

人的安排承担了一部分并发控制:尽量减少同时修改同一处内容的任务,明确怎样集成、怎样整理历史,以及完成以后要检查什么。

有界面的产出要及时打开验收。讲者在旁边放置持续刷新的浏览器,由自己观察结果并反馈,因此会要求 Agent 不再重复进行耗费 token 的浏览器或视觉检查。这是当时有人持续接管视觉验收的安排,不表示界面不需要验证。如果某个方向不对,应当尽早提出,避免多个 Agent 在同一个未经确认的假设上继续扩展。

这套方法的关键配合是:集中讨论建立共同前提,并行工作提高实现效率,快速反馈防止错误前提持续积累。

十三、官方讲义的延伸讨论:更高产能与新的版本管理

官方讲义进一步讨论了更高产能下的版本管理。本节按讲义整理,不把这些内容一概归为视频中的现场演示。

视频结尾实际提出了“如果 Agent 快了一千倍,版本控制怎么办”的问题,并明确留到下一讲继续讨论。下面的 MVCC、write skew 与 Jujutsu 等内容来自扩展后的官方讲义。

这个思想实验假设代码产能提高一千倍,而人的理解与验收速度没有同步提高;它不是已经测得的生产率。此时会发生什么?

Git 仍然可以存储提交,但人工阅读、协调和判断可能先出现积压。于是问题转成:能否从一开始就围绕机器生成的变化,设计版本控制与集成机制?

把变化如何组合放到中心

讲义使用 change algebra 描述一个方向:给变更建立组合规则。

需要回答的问题包括:两项变化是否独立,是否可以交换执行顺序,一项变化依赖哪个前置状态,冲突后应该重做哪一部分,以及怎样知道组合后的结果仍满足项目要求。

这比判断两个文件有没有修改同一行更进一步。文本冲突只是其中一种冲突;修改之间还可能存在语义依赖和共同约束。

MVCC 类比与文本合并的盲区

数据库的多版本并发控制(MVCC)提供了一个可比较的概念:不同工作可以基于自己看到的版本推进,再在状态生效时协调关系。但版本控制与数据库事务不完全相同,不能简单认为有了快照就解决了并发正确性。

官方讲义用值班员的例子解释这一点。假设要求至少一人在岗,起初甲、乙都在岗:

  1. Agent A 根据旧状态看到乙在岗,于是把甲改为离岗。
  2. Agent B 根据同一旧状态看到甲在岗,于是把乙改为离岗。
  3. 两项修改写在不同地方,文本合并没有冲突。
  4. 合并之后,两人都离岗,破坏了共同约束。

它类似数据库中的 write skew。每项变化在自己的观察前提下看起来合理,但组合起来不成立。可以自动合并,不能推出可以安全接受。

验证哪个版本,就必须发布哪个版本

讲义讨论的一种可能流程是:

  1. Agent 在独立快照上生成变化。
  2. 系统把变化与当前主线组合,形成候选版本。
  3. 针对这个候选版本执行验证。
  4. 在同一个原子检查与更新操作中,确认主线仍是验证所依据的状态,再发布那个确切的候选版本。
  5. 如果主线已经被其他工作推进,这次发布失败,重新组合和验证。

检查基线与发布必须放在同一锁区间,或者使用比较并交换等原子机制,不能检查完就退出保护,再无条件写入。这种设计把长时间的生成和测试放在锁外,只在读取或发布关键状态时使用短暂协调。否则,锁住整个开发过程,只会把所有并行任务重新排成队列。

其中仍有一个无法绕开的前提:要知道必须保持哪些不变量。如果值班约束从未进入规格和检查,系统不能仅凭没有文本冲突,就替我们推导出业务正确性。

Jujutsu 提供了哪些可研究的方向

讲义提到 Jujutsu 的几个机制:稳定的 change ID 可以跟踪修改或 rebase 后的变化;冲突可以作为一种状态保留;operation log 记录仓库操作,并支持操作恢复与并发协调。

这些机制适合用于思考机器参与开发时的工作方式,但不能直接等同于已经完成自动验证与准入的系统。变化如何组合、验收覆盖到哪里、组合失败后怎样修复,仍然需要额外设计。

人看大步变化,机器保留细节

进一步的设想是,面向人的历史可以集中展示有意义的结果,而机器保存更细的操作、尝试和验证证据。这样可能减少人理解过程噪声的负担,同时保留恢复依据。

讲者以 Agent 不能被开除或承担刑事责任的反问,挑战主要围绕追责建立的审计习惯:当修复足够便宜时,是否应该更重视正确性保障和恢复能力?

这是一项值得讨论的方向,不意味着所有错误都可忽略。它取决于错误能否及时发现、后果能否恢复、验收是否覆盖真实要求。人的工作仍然包括明确意图、不变量和完成标准;系统需要给出接受某项变化的依据。

十四、我的实践思考:让每次变化都留下判断依据

以下是基于本讲的个人整理,不是课堂原话。

对于自己的小项目,可以先把仓库整理到一个能够解释的状态:哪些是源码,哪些是构建输入,哪些是可重建的产物,哪些只属于当前机器。每个保留的文件最好都有明确职责,构建与验证入口也应该能够找到。

每次让 Agent 开始工作时,描述一个足够清楚的目标,并说明怎样确认完成。它交付之后,先看真实变化与运行结果,再决定提交边界。提交说明应该解释实际行为变化,不只是重复文件列表。

如果要并行处理多个任务,先确认共同接口与前提,把相互独立的工作分开。合入时重新验证组合结果,不把各自的测试成功简单相加成整个项目的正确性。

学习 Git 时,也可以给自己增加一个小习惯:执行命令前预测状态,执行以后用 status、diff 和历史验证。遇到与预测不符的结果,先弄清楚原因,再继续下一步。这样,每一次日常操作都能补充一个具体概念,而不必等到误操作时才开始理解工具。

未来的工具也许会减少我们直接操作 Git 的次数,但快照、引用、变更边界、依赖与并发不变量,仍然是理解系统如何共同工作的基础。

来源与整理说明

讲义中涉及的工具与案例,可继续查阅其原始材料:

官方讲义采用 CC BY-NC 4.0 许可。本文保留来源归属,对讲义内容进行了改写、重排与说明;扩展材料列为进一步核对和学习的入口,不表示本文独立复现了课堂工具、模型比较或实验。

音轨使用本地语音识别转录,并结合官方讲义与画面校正术语;自动转录不是可直接引用的逐字稿。课件与演示通过视频抽取的 100 张画面核对,抽样不能覆盖全部短暂画面,课件中的 AI 补充内容也不等同于讲者逐字口述。SWE-bench、Astropy 及 MVCC、write skew、Jujutsu 等段落按官方讲义作为延伸材料整理。