https://jyywiki.cn/GSE/2026/lect3.md read and summarize

回复 群友 607: 这是 GSE 2026 第三讲,主题是从零构建一个项目,并让它在持续修改中保持可理解、可恢复、可协作。两条主线:Git 这套版本管理机制,以及它背后的软件工程实践。 开场 老师谈 GPT-6 的使用感受:幻觉少了、品味提升了,但遇到设计问题还是得不断批评和反馈,所以有那句"我好像还可以再苟活几个月"。延续前两讲:从意图到规格再到实现,中间选择空间非常开放。你说"做个好用的界面",往往要看到第一版才知道自己真正介意什么,实现反过来帮我们确定需求。那模型进步这么快还学什么?答案是继续对世界建立正确的概念和理解:能看出一个设计好在哪里、知道该追问什么、能把大目标分解成可推进的工作。 项目最朴素的样子 一个目录加一堆文件:需求说明表达意图,接口文档和测试描述规格,源代码承担实现,构建脚本把它们组织起来,构建运行后得到可交付物,比如 npm run dev 起来的本地网页或一个可执行文件。老师的第一个博士生大一把代码存优盘,优盘坏了代码就没了;2012 年打包发邮件交作业还很自然。交作业方式变了,但"目录里存着文件"这套管理方式没有根本变化。 由此引出的问题:昨天能跑今天改坏了怎么回去?一个功能改一半突然要修别处怎么暂放?两人从同一份代码出发怎么汇合?于是需要版本管理:不只保存现在,还要保存经过哪些状态以及状态之间的关系。git clone 拿作业、git pull 收更新,而 make submit 是课程 Makefile 定义的入口,不是 Git 自带命令。 学习方法 virtually implement,先在脑子里做一遍:读论文先想换成我会怎么解、需要哪些数据结构、在哪可能失败,再看作者方案;一致就比细节,不一致就追问差异来自哪个约束或哪条自己不知道的经验。读代码、文档、甚至 AI 写的一段话都一样,问自己能不能预测下一段。课上那句"上课耽误学习"是说课外信息密度已经很大,主动发现自己不知道什么的意识更重要。 保研面试的真实例子:什么是 Git?什么是 git rebase?merge 冲突后你会观察到什么?第一个多数人还能说几句,rebase 十个人全没答出来,也没人主动说文本冲突的双方内容和标记会直接出现在工作区文件里。这不只是少背术语,而是用过一个工具并不等于留意过眼前发生了什么。 Git 的核心概念 Git 管理的是项目的快照。以项目为单位,因为源码、接口、配置常常要一起变,只恢复旧代码留下新配置,可能拼出历史上从没正常运行过的版本。commit 把快照和作者、说明、父提交联系起来,父提交让一个个状态构成历史。编辑器里"存过盘"和 Git 里"提交过"保存的是不同层次的状态。Git 也不机械复制全部文件,未变化的部分复用对象,逻辑上是完整快照,存储上共享数据。 三个区域要分清:工作区、暂存区(Index)、提交历史。git add 把此刻文件内容放进暂存区,git commit 据此创建提交。所以那个经典问题:改 hello.c、add、再改、commit,提交进去的是 add 时的版本,后续修改还在工作区。git diff 看未暂存的修改,git diff --cached 看暂存区相对当前提交的变化。 分支是指向某个提交的可移动引用,通常 HEAD 表示当前分支。git merge 结合共同祖先和双方变化,分叉时常见结果是两个父提交的合并提交,一方只是另一方后续则是 fast-forward。文本冲突时 Git 会在工作区文件里写 <<<<<<<、=======、>>>>>>> 标记,git status 报 both modified,解决就是读上下文改成最终内容、删标记、git add 再完成合并;二进制、删除与修改冲突不一定有同样的文本标记,所以状态和文件都要看。git rebase 把一串提交的改动依次重放到新起点,重放后的提交通常有新标识,过程中也可能冲突;整理自己的提交可以,但别人已经依赖的提交再改写就会增加协作成本。 关键是操作前能预测:工作区会变吗?下次提交包含什么?哪个引用会移动? Best practices 这一层 比如目录里 hello.c 和编译出的 hello 要不要都提交?都存就可能出现两份不一致的状态,只存源码也得说清怎么构建——"我的目录里有什么"和"项目需要共同保存什么"是两个问题。可以在自己的 AGENTS.md 里写一条学习约定:做任何 git 操作前逐个文件检查变化,替我解释是否符合 best practice、为什么、相关概念。dist/、编译产物、调试日志、.DS_Store 这类不提交;但依赖锁文件常要提交,有些项目有意保存生成的头文件。判断标准是这个文件是否构成共同维护的项目状态、怎么保证它和其他文件一致。.gitignore 不会让已跟踪文件自动退出版本管理,也不会抹掉历史,所以误提交密钥后光删当前文件不够,旧提交里还在。 还有一个很小的设计选择:文档检查规则写成 TYPO-CHECK 还是 typo-check。方括号里是固定规则标识,后面是解释规则的自然语言,大写让两者更容易视觉区分、引用某条规则时也更像明确指代;但如果项目已统一小写或工具要求小写,一致性可能更重要。得到的不是"大写永远更好",而是理解了角色区分、可引用性和一致性这几个考量。 课堂还用 gitv 举例:Node.js 命令行工具,接收真实仓库路径、起 HTTP 服务、在浏览器里把 blob、tree、commit 的关系和变化演示出来,配合"先预测再观察"。拿它跟 Learn Git Branching、61A Code 比较时,比较要落在细节上:关键状态看不看得清、交互顺不顺、边界情况处理得怎么样。一次生成结果只代表这次任务的表现,不能推成模型之间的普遍排名。老师对"只读过介绍却从没真正打开好东西"很不满意。 commit message 同理,update 几个月后毫无帮助,规范一点可以写 fix(parser): accept repeated spaces between arguments,标题说行为变化,正文补为什么改、想保留什么。diff 展示怎么改,提交说明解释为什么改。 这些实践最后归到四个工程问题:留退路(及时打检查点,reflog 找引用移动,远端不会自动备份你没提交的编辑)、让别人读懂(围绕明确意图组织提交,修 bug 和回归测试可以在一起,顺手格式化整个项目一般分开)、让协作可预期(哪些历史可以整理、谁负责合并、怎样发布)、让约定得到执行(hooks 和 CI)。 SWE-bench 和真实修 bug 经典 SWE-bench 从真实 GitHub 项目的 issue 和修复构建任务:给已有仓库状态加问题描述,让模型产出补丁。评测里 FAIL_TO_PASS 要从失败变通过,PASS_TO_PASS 要继续通过,这两个名字说明了修复的双重要求。Astropy 的例子很具体:建模工具用布尔矩阵表示输出对输入的依赖,两个独立一维线性模型并排组合本该只有对角线为真,可先组成子模型再跟另一个组合后对应区域变成全真,根因是拼接依赖矩阵时把右侧填成 1、丢掉了内部结构,修复就是把填入值改成 right,PR 同时补了不同嵌套方式的测试和更新记录。"只改了一行"说的是最终 diff 的大小,找到这一行要理解用户行为和项目抽象,要让项目接受它得留下验证证据,要让用户得到它还得完成集成与交付。平台侧协作可以用 gh、glab 这类命令行工具串起来。 这一讲的核心概括是:项目是由原子变更推进的快照,可以分支,可以合并。原子变更指围绕一个明确意图组织的完整修改,能被解释、验证、必要时单独撤销,不一定只改一个文件。因为人很难处理"一团混沌":改了接口、重写实现、更新依赖、调整格式然后测试失败,从哪找起?git bisect 靠测试缩小区间定位引入问题的提交,git revert 记录撤销。线性历史便于阅读和 cherry-pick、blame,但每一步意义不明、不能运行,排成一条直线也不会自动变成好历史。 Agent 参与的开发 架构阶段先"单线程":和强模型反复讨论到接口、约束、总体设计收敛,再写成文档、规则、示例,包括测试要做到什么程度。这个阶段仍然需要人的知识,学操作系统、编译原理、数据库是因为这些领域积累了怎么划分职责、表达不变量、隔离变化、协调并发的好设计。实现阶段可以开少量终端并行(老师自己的做法是不超过三个,这是个人工作方式不是普遍上限),用 AGENTS.md 说明协作规则,人脑承担一部分并发控制、尽量安排互不冲突的任务,每轮产出尽快检查、有界面就立刻打开给视觉验收反馈。不管这次质量高不高,别让未确认的设计选择积累太久。 最后是个思想实验:Agent 产码速度提高一千倍会怎样?生成一分钟、人工理解和验收一小时,积压的就是等待判断的变更,Git 还能保存,但围绕人工提交、审查、协调组织起来的流程会成为瓶颈。于是设想为 Agent 设计版本控制:把快照、变更以及变更如何组合放到中心,也就是 change algebra——两项变化能否独立应用、能否交换顺序、是否依赖某个前置变化、冲突后该重做哪部分。这跟数据库 MVCC 有相通之处但不是一回事。例子:项目要求至少一名值班员,初始甲乙都在岗,Agent A 看到乙在岗把甲改离岗,Agent B 看到甲在岗把乙改离岗,两项修改在不同文件、文本合并没有任何冲突,结果却无人值班,类似 write skew——快照和局部检查之外还要检查组合结果。一种可能设计是让 Agent 在独立快照上生成变更,系统把变更与当前主线组合、验证候选结果、原子地发布这个确切版本,发布时确认主线还是验证所依据的版本,锁只保护短暂的版本读取或发布,耗时的生成和测试在锁外。Jujutsu 更接近把变更放中心的思路:稳定的 change ID 跟踪被修改或 rebase 的变更、允许把冲突作为状态保存、operation log 记录仓库操作并协调并发。 收尾是:人看到的历史也许只保留有意义的大步,机器保存更细的操作和验证证据;测试全绿只说明已有检查通过了,如果"至少一人值班"从没写进要求或检查,就不能指望文件合并替我们保证;人的责任会更多落在定义意图、不变量和完成标准上,系统则要能解释它凭什么接受这次变化。 哪部分想细看(暂存区、rebase、SWE-bench、change algebra)我可以再展开。