[00:00:00] 好的,在前几次课的时候我们讲了怎么用AI的一些案例,对吧,就像我刚才给大家现场教学 [00:00:08] 了一下你们应该怎么把AI整合到你们生活的流程当中,因为你们在生活中会遇到无穷多这种不顺心, [00:00:15] 就是你用很多东西的时候都会感到不顺心,但是AI作为一个非常强大的assistant, [00:00:21] 它可以帮你把不顺心的事情给挡掉,你只需要告诉它你的意图,真实的意图是什么就可以了, [00:00:28] 然后你甚至还可以蒸馏成skills [00:00:29] 然后让这个过程变得更平顺 [00:00:32] 而且在这个过程当中 [00:00:34] 我们不断地看到这个新的模型的进展 [00:00:38] GPT-6就真的发布了 [00:00:40] 大家用上了吗 [00:00:41] 然后我用了一下 [00:00:43] 反正还是有惊喜的 [00:00:44] 虽然说可能编程上的提升有限 [00:00:48] 它没有在编程上说的我那种 [00:00:51] 我说我们现在有经验的软件工程师 [00:00:55] 还有比较好的品位 [00:00:56] 那么这个品位可以指导AI做出更好的东西 [00:00:59] 现在AI更多的时候是完成任务 [00:01:02] 好像GPT-6还没有学会这种非常好的品位 [00:01:07] 我经常还要骂它 [00:01:07] 所以我觉得我可能还能再苟活个几个月 [00:01:10] 但是确实它在各方面的能力都提升了 [00:01:13] 而且做更复杂的任务的时候 [00:01:15] 它变得更快了 [00:01:16] 省很多token [00:01:16] 我特意measure了一下 [00:01:19] 真的是这样的 [00:01:20] 当然了这也是一个 [00:01:23] 我觉得就要在这门课上讨论的问题 [00:01:25] 就是因为为什么 [00:01:26] 什么时候我们会觉得 [00:01:28] 这个GPT做得不好 [00:01:29] 还是因为我们面对了一个 [00:01:30] 非常开放的 [00:01:31] Intent 和 Spec、 [00:01:32] Implementation 的空间 [00:01:33] 你脑袋中的 [00:01:35] 就比如说同样的一个任务 [00:01:37] 如果我告诉你 [00:01:38] 就给我做一个国民级的应用吧 [00:01:40] 那么每一个人 [00:01:41] 哪怕是顶级的产品经理 [00:01:43] 他脑袋里面想的都不一样 [00:01:45] 然后AI是没有办法和人脑 [00:01:47] 去直接做这样的一个对齐的 [00:01:49] 所以他总一定会 [00:01:52] 按照他自己的方式去漂移 [00:01:53] 而且我发现 [00:01:54] AI写出来的代码 [00:01:55] 人类越来越难读了 [00:01:56] GPT-6我不知道 [00:01:59] 因为受它这个 [00:01:59] 思维链变短的影响 [00:02:01] 它思维链变得非常紧凑 [00:02:02] 然后现在写的代码 [00:02:03] 也非常紧凑 [00:02:04] 就你看明显的 [00:02:05] 从比如说GPT-5开始 [00:02:06] 到5.6 [00:02:07] 然后到6 [00:02:07] 然后它写代码 [00:02:08] 就写得越来越紧凑 [00:02:11] 然后你越来越不想看它 [00:02:12] 就人没有办法 [00:02:13] review那个代码 [00:02:16] 曾经好像我们会 [00:02:17] 各种各样的prompt [00:02:18] AI说你写出 [00:02:19] 符合人类工程规范的代码 [00:02:21] 但好像现在我们觉得 [00:02:22] 我们再也不会看 [00:02:24] AI写的代码 [00:02:24] 所以就随他去吧 [00:02:25] 他反正能把代码弄对 [00:02:29] 然后我觉得GPT-6 [00:02:31] 给我们最大的惊喜 [00:02:32] 是它的空间感知和理解的能力 [00:02:37] 我一直认为 [00:02:38] OpenAI对AGI的理解是这样的 [00:02:42] 首先AI得学会数学 [00:02:45] 因为数学是所有人类 [00:02:47] 世界里面最难的那种语言 [00:02:49] 因为它 [00:02:50] 我上第一次课的时候说 [00:02:51] 它有一个世界上所有人都认同的一个推理系统 [00:02:56] 所以它和我们说的软件工程里面的 [00:03:01] Intent, Spec, Implementation的Gap [00:03:03] 是反过来走的 [00:03:04] 数学是有全球性的共识的 [00:03:06] 你一个定理你能证明出来 [00:03:09] 那么全球的人都会认为这个定理的证明是对或者不对 [00:03:13] 所以它是一个非常好的能grounding的东西 [00:03:15] 而且数学的逻辑推理是可以解释 [00:03:19] 我们人类生活里面的各种行为 [00:03:22] 就比如说 [00:03:22] 你会觉得好 [00:03:23] 今天中午吃什么 [00:03:25] 你们马上就会面临这个问题了 [00:03:26] 去几食堂 [00:03:27] 那这个问题 [00:03:29] 你们可能会觉得 [00:03:30] 这是一个人类世界里面的问题 [00:03:33] 必须是你的灵魂来给出答案的 [00:03:35] 但其实不完全 [00:03:36] 因为你的大脑里面 [00:03:38] 其实不停地在投票 [00:03:39] 比如说我就会选择 [00:03:40] 在那个逸夫楼订饭 [00:03:41] 因为对我来说 [00:03:42] 这是最快的这个选项 [00:03:44] 因为我下午还要开会 [00:03:46] 然后如果我时间能来得及的话 [00:03:48] 我中午可以小睡一会 [00:03:49] OK [00:03:49] 所以你看我其实是在各种因素之间投票 [00:03:53] 然后在这个因素之间 [00:03:55] 比如说什么事情A imply B [00:03:57] 如果我做了A就会做B [00:03:59] 这其实是一个数学 [00:04:00] 我可以ground到你们在离散数学里面 [00:04:02] 学命题逻辑里面的各种各样的东西 [00:04:05] 所以OpenAI应该是说 [00:04:07] 我先把数学会 [00:04:09] 好 现在数学它已经学会了 [00:04:10] 然后学会了以后 [00:04:11] 它再去强化学习别的能力 [00:04:13] 我一直觉得就是比如说空间感知 [00:04:16] 它不是这种绝对学不会的 [00:04:19] 我就这样想 [00:04:20] 比如说给你一个鲁班锁 [00:04:21] 很复杂的结构 [00:04:23] 然后我就问你 [00:04:24] 你按照什么样的方向 [00:04:27] 可以把第一块东西给抽出来 [00:04:30] 然后你们人脑就会开始 [00:04:33] 你还做过那种智力题吗 [00:04:35] 我觉得你们从小就会做 [00:04:36] 说给你一个立方体 [00:04:39] 或者一个什么奇怪形状的投影 [00:04:41] 就有些那种benchmark里面 [00:04:43] 就是这样的 [00:04:43] 然后问哪一块和它是一样的 [00:04:47] 或者给你一个骰子 [00:04:48] 六面的骰子 [00:04:49] 问这个哪一个展开图 [00:04:51] 对应了你看到那个骰子 [00:04:53] 然后你就在你的脑袋里面 [00:04:54] 做这种空间想象 [00:04:56] 然后这种空间想象 [00:04:57] 你只要从简单的任务开始 [00:04:58] 给它强化学习 [00:04:59] 肯定是能做到的 [00:05:01] 然后马上 [00:05:01] 我当时看到这个例子 [00:05:04] 那时候GPT-6还没有发 [00:05:05] 然后我就心疼我的这个PhD [00:05:09] 因为我们已经做了小半年的 [00:05:11] 这个3D打印的工作了 [00:05:12] 然后我们可以在GPT-5.6的 [00:05:14] 空间感知能力很差的情况下 [00:05:16] 然后可以通过直接对话 [00:05:18] 可以生成一个还是很像样 [00:05:20] 而且各方面都能符合规范的 [00:05:22] 这个3D打印模型 [00:05:25] 我们还是有一些比较killer的application [00:05:26] 比如说我们可以做一个那种小的 [00:05:28] 我不知道你们有没有玩过那种光栅玩具 [00:05:31] 就是那种光栅尺 [00:05:32] 你从左边看和右边看 [00:05:34] 是两个不同的这个图案 [00:05:36] 然后比如说你可以说我要做这个 [00:05:37] 然后我也可以很长的那个搜索树 [00:05:40] 然后帮你做一个光栅尺 [00:05:41] 然后结果这个昨天 [00:05:43] GPT-6 one shot [00:05:44] 直接出直接搞定 [00:05:47] 然后我们现在还在想 [00:05:50] 想怎么样解决这个问题 [00:05:53] 然后同样的是 [00:05:54] 因为它有了空间的感知能力 [00:05:55] 又有数学的能力以后 [00:05:57] 它在具身赛道 [00:05:59] 就很明显是可以做得很好的 [00:06:02] 所以第二天你就看到 [00:06:04] 很多人在小红书上发 [00:06:05] 它直接用GPT-6 [00:06:07] 去控制一个机械臂 [00:06:09] 去做这种抓取 [00:06:10] 或者叠衣服这样的复杂的动作 [00:06:13] 而注意这个GPT-6是一个语言模型 [00:06:16] 它是 [00:06:17] 虽然它有视觉的能力 [00:06:19] 它就是一帧一帧的问你下面要干什么 [00:06:22] 还没有解锁这个提示词的engineering [00:06:26] 它就已经做得相当的好的 [00:06:28] 已经达到了一个state of the art的能力范围了 [00:06:32] 所以模型一定会进步的 [00:06:34] 这个例子告诉大家 [00:06:35] 也许在我上课上到某一次的时候 [00:06:39] 我突然就可以说这个课不用上了 [00:06:42] 这样也好 [00:06:43] 然后大家就可以去做产品了 [00:06:47] 到今天我觉得我们还是 [00:06:49] 当然需要学习一些软件工程的知识的 [00:06:54] 因为AI [00:06:55] 我刚才说AI解锁的是你的执行力 [00:06:59] 我举这样的一个例子 [00:07:00] 因为我正好被人问到 [00:07:02] 我们在AI的时代 [00:07:04] 还要不要学数据库 [00:07:05] 学操作系统 [00:07:06] 学这些东西 [00:07:08] 然后我的答案其实也不是特别的确定 [00:07:12] 因为我打个这样的一个比方 [00:07:14] 我们回到一百多年以前 [00:07:16] 汽车刚发明的时候 [00:07:17] 那你汽车开个一两公里就会出故障 [00:07:20] 所以你的每一个司机 [00:07:22] 每一个driver [00:07:23] 你都必须对汽车的工作原理 [00:07:25] 了解得非常清楚 [00:07:26] 因为你的汽车一旦出故障 [00:07:28] 你得知道它是怎么工作的 [00:07:30] 那在那个时代 [00:07:31] 我们把人回到那个时代 [00:07:33] 那这就是理所应当的事 [00:07:35] 你想要 [00:07:36] 你要开车 [00:07:37] 你就需要掌握汽车的基本原理的知识 [00:07:40] 你要学computer science [00:07:41] 你要去软件工程的工厂 [00:07:43] 那你就要去学操作系统 [00:07:46] 学数据库 [00:07:46] 把这些技术知识做好 [00:07:47] 但你想今天我们怎么开车 [00:07:49] 今天我们在地下车库 [00:07:52] 直接说帮我开到什么地方 [00:07:55] 然后自动驾驶汽车就出发了 [00:07:57] 然后这你是不可能 [00:07:59] 或者说你作为一个人 [00:08:00] 已经不理解这个整个系统站上面 [00:08:02] 是怎么工作的了 [00:08:04] 因为最终它的动力 [00:08:06] 还是传递到一个机械装置 [00:08:08] 和那个一百多年的汽车 [00:08:09] 还是有一点相似的 [00:08:12] 但是在这个过程中 [00:08:13] 他经历了 [00:08:15] 所有的东西 [00:08:15] 你都不需要知道 [00:08:16] 你就可以把汽车开起来 [00:08:18] 换句话说 [00:08:18] 人工智能你也可以想象成是 [00:08:19] 它是一个 [00:08:20] 类似于汽车这样的东西 [00:08:23] 当然它比汽车更强 [00:08:24] 它替代的不仅是 [00:08:24] 人的这个腿部的 [00:08:26] 向前走的能力 [00:08:27] 它替代人的 [00:08:28] 人的势力以后 [00:08:29] 那我们 [00:08:31] 我们是不是还要学 [00:08:32] 当然我今天的答案 [00:08:33] 肯定是要学 [00:08:34] 因为你们学了以后 [00:08:35] 你可以更好地解锁 [00:08:36] 这个AI的创造力 [00:08:38] 但是以后应该怎么学 [00:08:40] 我现在还暂时是 [00:08:42] 暂时是不知道的 [00:08:43] 你们也许 [00:08:45] 如果你没有答案的话 [00:08:46] 也可以欢迎和我来探讨 [00:08:48] 当然这是我的个人的观点 [00:08:51] 你们至少在现在的阶段 [00:08:52] 学习还是有好处的 [00:08:55] 你理解了正确的概念 [00:08:57] 可以帮助你 [00:08:58] 从别的地方借鉴一些知识 [00:09:00] 来创造更好的东西 [00:09:03] 然后我今天就会进入 [00:09:04] 真正的软件工程 [00:09:05] 我们说生成式软件工程 [00:09:07] 那你如果要开始做软件工程 [00:09:10] 开始vibe coding [00:09:12] 你干的第一件事情是什么 [00:09:16] 你不是从网上 [00:09:19] 或者说比如做作业的时候 [00:09:20] 那你一定是从课程网站上 [00:09:22] 或者老师的要求的什么地方 [00:09:23] 下载一个东西 [00:09:24] 然后从那里开始工作 [00:09:25] 那如果你今天就真的是想要 [00:09:27] 做一个项目 [00:09:28] 那你做的第一件事 [00:09:30] 一定是先建一个目录 [00:09:32] 建一个目录 [00:09:32] 然后给它起一个名字 [00:09:34] 可能起一个中文的名字 [00:09:36] 叫我的最酷的guild game [00:09:38] 然后你就开始vibe coding [00:09:42] 那什么是一个软件工程的项目 [00:09:45] 你创建了以后 [00:09:47] 你面对一个空的目录的时候 [00:09:49] 你们还记得你们是怎么学会 [00:09:51] 应该做什么的 [00:09:54] 你们可能会看到 [00:09:55] 老师在上课的时候 [00:09:57] 打开了一个工程项目 [00:09:59] 然后里面有一些文件 [00:10:00] 你就在不经意之间 [00:10:01] 记住了一些这个项目的东西 [00:10:04] 然后你可能会 [00:10:06] 老师会向你推荐 [00:10:07] 或者同学会推荐 [00:10:08] 什么什么东西发布了 [00:10:10] 你可以去看 [00:10:10] 然后你得到了一个什么GitHub [00:10:13] 或者Gitee的链接 [00:10:14] 然后你打开以后 [00:10:15] 那是一个网页 [00:10:16] 网页还是非常人类friendly的 [00:10:19] 你看到那个网页 [00:10:20] 网页上面有一些文件 [00:10:22] 你就知道 [00:10:23] 我的一个目录里面 [00:10:24] 会要有一个文件 [00:10:26] 所以当我们说 [00:10:27] 我们要开始一个软件工程项目的时候 [00:10:29] 你就会建一个这样的目录 [00:10:32] 它里面有一堆文件 [00:10:33] 然后这堆文件共同构成了一个可以交付的东西 [00:10:38] 这个东西最后会变成你的产品 [00:10:40] 这个目录它可能是直接在你本地运行的 [00:10:43] 在你神奇的跟agent说 [00:10:46] 请你帮我运行的时候 [00:10:47] 那个窗口就弹出来 [00:10:48] 或者网页就弹出来 [00:10:50] 一个著名的笑话说 [00:10:51] GPT-6太牛逼了 [00:10:52] 我又做了多少个killer级的产品 [00:10:55] 他们是HTTP localhost下的5173 [00:11:00] 然后发一个Twitter [00:11:04] 所以你还是需要知识的 [00:11:06] 你在你本机可以打开它 [00:11:08] 但是世界上的其他人打不开它 [00:11:10] 因为如果你不知道 [00:11:11] Localhost是你的本机的话 [00:11:16] OK 当然你可以通过 [00:11:17] 把你的intent specification [00:11:19] 说得更清楚一点 [00:11:20] 如果你在你的intent里面 [00:11:22] 比如说在那个文档里面 [00:11:23] 就说明我的这样的一个东西 [00:11:25] 要被全世界人看到 [00:11:27] 如果你不说 [00:11:29] GPT可能会把你当成一个开发者 [00:11:31] 那开发者最正常的 [00:11:32] 一定是在你本地先调试 [00:11:33] 先起一个本地的development server [00:11:36] 然后你可以看到它的结果 [00:11:38] 直到你觉得ready了 [00:11:39] 你才会把这个东西送到远端去 [00:11:41] 但这都是知识 [00:11:42] 你们在计算机 [00:11:44] 你们是computer science的学生 [00:11:45] 上各种各样课的时候 [00:11:47] 反复地学习这些知识 [00:11:48] 然后这些知识最后 [00:11:49] OK [00:11:50] 变成这个 [00:11:52] 对 变成这个 [00:11:55] 它里面有文件 [00:11:57] 最后会得到一个可交付的东西 [00:11:59] 然后你们会从最开始学编程 [00:12:03] 可能是你在Visual Studio里面 [00:12:05] 一键编译和运行 [00:12:07] 然后到后面你们学的时候会知道 [00:12:08] 你的Build的目录里面 [00:12:13] 所有的东西都是由某些命令行的工具创建出来的 [00:12:16] 你们会开始在大二的时候 [00:12:17] 用一些像GCC这样的命令行工具 [00:12:20] 会把一个hello.c变成hello的一个binary [00:12:24] 这就构成了一个软件工程的项目 [00:12:26] 所以你们会听到这样的一个标准的定义 [00:12:31] 叫什么叫软件 [00:12:34] 这是一个很好的问题 [00:12:35] 以前老先生喜欢问我们什么是软件 [00:12:37] 软件是一个如此基础的东西 [00:12:39] 到处无处不在的软件 [00:12:42] 你想没有软件 [00:12:43] 你们今天看到这个是黑的 [00:12:45] 你不光你不看到我这个 [00:12:46] 你就连这个台子也启动不了 [00:12:48] 我这个升降 [00:12:49] 就连我的这个升降桌 [00:12:51] 这个教室还是不错的 [00:12:52] 有一个可以升降的升降桌 [00:12:55] 你看如果没有软件 [00:12:57] 我这个升降桌都是升不起来降不起来的 [00:12:59] 这个升降桌 [00:13:00] 这边还有一个高度的显示 [00:13:02] 现在是106厘米 [00:13:05] 那么软件到底是什么 [00:13:07] 有个标准定义叫什么代码文档等 [00:13:12] 一切和软件相关的东西的集合 [00:13:16] 好像有一个这样的definition [00:13:18] 但其实我一直说软件是人类世界的需求 [00:13:22] 在信息世界有一个投影 [00:13:23] 然后这个投影 [00:13:24] 今天看到是一个目录 [00:13:26] 然后目录里面有各种这样的东西 [00:13:30] OK [00:13:30] That's all [00:13:31] 你知道目录里面应该有什么 [00:13:33] 比如说有个hello.c [00:13:34] 然后你可以编译出一个hello [00:13:35] 你就已经在做软件了 [00:13:37] 当然这个可能不符合 [00:13:39] 现代软件工程的 [00:13:40] 这个best practice [00:13:42] 但古早的时期 [00:13:43] 我觉得就甚至是十年以前 [00:13:44] 大家真的是这样的 [00:13:46] 我给大家讲一个 [00:13:46] 我的第一个PhD student的故事 [00:13:49] 他应该是2012年入学的 [00:13:52] 进入南大的 [00:13:53] 我第一个博士 [00:13:56] 他大一的时候 [00:13:57] 他发现了一个非常 [00:14:00] 非常好的学习的方式 [00:14:02] 因为他经常会去机房 [00:14:03] 他可能他自己的笔记本电脑 [00:14:05] 还是屏幕比较小 [00:14:06] 也不是很舒服 [00:14:08] 不像现在我相信 [00:14:09] 大家应该是都会买一块屏幕 [00:14:11] 应该不贵 [00:14:12] 不是很贵 [00:14:13] 甚至淘一个二手的屏幕 [00:14:14] 只要小几百块钱 [00:14:16] 然后你就可以在宿舍 [00:14:18] 获得非常好的编程体验 [00:14:19] 但那个时候好像 [00:14:20] 我们的标准答案是去机房 [00:14:22] 屏幕好像没有那么便宜 [00:14:23] 那去机房的话 [00:14:24] 他就会来回拷贝代码 [00:14:27] 比较麻烦 [00:14:28] 所以他想出了一个妙招 [00:14:30] 大一的时候 [00:14:31] 把代码保存到U盘上 [00:14:32] 你们多多少少 [00:14:34] 可能也干过这样的事 [00:14:35] 或者在中学的时候 [00:14:36] 上电脑课的时候 [00:14:37] 你做的作品 [00:14:38] 你会保存在U盘上 [00:14:40] 然后结果到他大二的时候 [00:14:41] 这个U盘坏了 [00:14:42] 然后U盘坏了以后 [00:14:44] 代码全部都没有了 [00:14:46] 作业不见了 [00:14:47] 然后他珍藏的那些 [00:14:48] 启蒙的时候的作业 [00:14:50] 作业不见了 [00:14:51] 有一点伤心 [00:14:51] 但是好像也没有太大的关系 [00:14:56] 我们很长时间都是这样管理代码的 [00:14:59] 比如说我记得我上学的时候 [00:15:00] 交作业的方式是打一个压缩包发邮件 [00:15:03] 然后现在好像你们会在一个网页上面上传 [00:15:08] 这都是管理项目的方式 [00:15:10] 但是软件工程 [00:15:13] 当我们谈软件工程的时候 [00:15:14] 今天是有标准答案的 [00:15:17] 就是你应该去用Git [00:15:21] Git是一个除了能够保存一个目录 [00:15:25] 就是项目是一个目录 [00:15:27] 目录里面有hello.c [00:15:28] 有hello的binary [00:15:29] 还有文档等 [00:15:30] 它还能保留比较和恢复历史的版本 [00:15:35] 这是一个很重要的软件工程的特性 [00:15:38] 我后面会详细展开来讲 [00:15:40] 然后有了Git以后 [00:15:41] 其实很多事情都变得很容易 [00:15:44] 比如说我再也不需要发邮件了 [00:15:46] 我真的很难想象 [00:15:48] 我们过去 [00:15:50] 今天是agent的时代 [00:15:51] 我有了十倍一百倍的生产力集成 [00:15:53] 我只要让他去帮我写一个投诉 [00:15:57] 他就 [00:15:59] 我就什么也不用管了 [00:16:00] 比如说如果我今天是用我的codex [00:16:03] 我就真的选择信任他 [00:16:05] 我真的就不管了 [00:16:06] 这事情就过去了 [00:16:07] 我看都不会 [00:16:08] 甚至我看都不会看 [00:16:09] 我让他把 [00:16:10] 结束以后把这个浏览器关掉 [00:16:12] 但过去的时候 [00:16:13] 真的我们还在用邮件管理项目 [00:16:17] 然后后来有了Git以后 [00:16:18] 我们发现效率高了很多 [00:16:20] 我们可以用Git clone [00:16:21] 去从一个远端获取一个作业 [00:16:25] 然后如果作业更新了 [00:16:26] 我可以在远端更新 [00:16:28] 我就不需要给每一个同学 [00:16:29] 都发一个什么 [00:16:32] 我只需要在上课的时候 [00:16:33] 告诉同学们说 [00:16:34] 我的作业更新了 [00:16:35] 你们用Git pull去下载 [00:16:37] 然后以及我的 [00:16:39] 比如说我上课的时候 [00:16:40] 我有一个make submit [00:16:41] 它会自动再用一个脚本 [00:16:43] 把包打好 [00:16:44] 打好以后上传到我的课程网站 [00:16:46] 非常的方便 [00:16:47] 这有点像一个现在的CI/CD的流程 [00:16:50] CI/CD我应该专门会有一次课来讲 [00:16:55] 然后这就引出了一个 [00:17:00] 我觉得这还是我几乎我每节课都会要讲的事情 [00:17:05] 当我们今天学习变容易了以后 [00:17:09] 这门课上讲的知识都不重要 [00:17:11] 重要的其实是你们的学习意识 [00:17:13] 比如说你们回想一下 [00:17:15] 你们都看过很多Git的项目 [00:17:19] 肯定的 [00:17:20] 因为你哪怕是打开课程作业 [00:17:22] 你都会看到它有一个Git的 [00:17:24] GitHub的链接 [00:17:26] 那么你们有没有留意过 [00:17:27] 不同的项目 [00:17:28] 它们之间细微的区别 [00:17:31] 比如说有的项目有这个文件 [00:17:32] 有的项目没有那个文件 [00:17:35] 然后有的项目里面文档是这样写的 [00:17:37] 有的项目文档是那样写的 [00:17:38] 你在这种 [00:17:40] 不停地有这种信号进入的时候 [00:17:42] 其实你就能判断出 [00:17:44] 我们应该怎么用Git [00:17:47] 我不知道大家 [00:17:49] 就总有一种感觉是 [00:17:50] 然后老师说 [00:17:52] 这个是不是跟中学的时候有关系 [00:17:54] 因为老师说的事情实在是太多了 [00:17:56] 在中学的时候 [00:17:59] 老师你必须要听老师的话 [00:18:01] 你才能把所有的事情做完 [00:18:03] 如果你不听老师的话 [00:18:03] 你就不可能把事情做完 [00:18:04] 但到了现在 [00:18:06] 比如说你去GitHub学习的时候 [00:18:10] 如果你还带着这个心态 [00:18:11] 老师说 [00:18:11] 你去把作业拉下来 [00:18:14] 那你就有点像AI [00:18:17] 就有点像新鲜干活的AI [00:18:19] 你被强化学习成了一个 [00:18:21] 当老师说让你做这件事的时候 [00:18:23] 你只做这件事 [00:18:24] 绝对不会多碰一点点的 [00:18:26] 因为这是最省时间的 [00:18:28] AI也被调成这样了 [00:18:30] 但如果你花一点时间 [00:18:32] 愿意 [00:18:32] 其实我就劝大家 [00:18:33] 愿意多花一点点时间 [00:18:35] 去点进去看一下里面的各种东西 [00:18:38] 然后你可能突然间就有一个灵光一现 [00:18:41] 这个文档写得很好 [00:18:42] 因为你马上 [00:18:44] 比如今天 [00:18:45] 你看到满世界的GitHub上面的项目 [00:18:48] 全部都是巨长无比的README [00:18:51] 然后README里面有各种 [00:18:52] Unicode字符组成的 [00:18:55] 什么目录结构 [00:18:56] 框图 [00:18:58] 就这种AI Slop的感觉 [00:18:59] 你马上就有了 [00:19:00] 你们有没有留意过这些细节 [00:19:03] 然后在这些 [00:19:04] 你看过这么多AI Slop的时候 [00:19:05] 突然你发现了一个什么老项目 [00:19:07] 十几年前开始维护的老项目 [00:19:10] 点进去那个文档 [00:19:11] 清新舒服 [00:19:12] README很短 [00:19:14] 但是README看完以后 [00:19:15] 你想看的东西三个链接 [00:19:18] 你就走过去了 [00:19:19] 你不需要看那么长的AI [00:19:22] 你就学会了文档应该这样写 [00:19:26] 因为我不知道你们有没有机会 [00:19:30] 比如说去对比不同的城市的路牌 [00:19:33] 然后我就觉得是很有意思的 [00:19:36] 为什么我们有时候觉得 [00:19:38] 中国是一个developing country [00:19:39] 是一个发展中国家 [00:19:41] 意味着我们提升的空间很大 [00:19:45] 我觉得我有一次 [00:19:46] 那已经好多好多年以前了 [00:19:49] 我特别getting impressed [00:19:51] 我在香港的时候 [00:19:52] 我发现我要找路 [00:19:54] 然后我在找路的时候 [00:19:55] 我发现每当我有不确定性的时候 [00:19:57] 比如说我前面有三个口的时候 [00:20:00] 我正前方一定有一个路牌告诉我 [00:20:03] 三个口是什么 [00:20:05] 从来没有过 [00:20:06] 它有一个非常复杂的地下系统 [00:20:08] 然后有的时候你回到南京的时候 [00:20:09] 你发现你有的时候经常会要在地铁站里找 [00:20:14] 我下一步 [00:20:15] 我不知道了以后要到哪一个口的时候 [00:20:17] 你需要环顾四周去寻找 [00:20:19] 甚至要移动比较远的距离 [00:20:21] 找到一个地图再去看 [00:20:24] 然后你其实这是一个学习的过程 [00:20:26] 我觉得很好 [00:20:27] 如果有一个 [00:20:28] 对大家来说有一个范例比你做的好 [00:20:31] 然后你就学到了 [00:20:33] 我其实在这个对比当中 [00:20:35] 我就学到一个lesson [00:20:36] 比如说如果我要做一个UI设计的话 [00:20:38] 我的这个UI就应该是 [00:20:42] 任何人 [00:20:43] 我会要站在人的角度 [00:20:45] 人站在这个页面上的时候 [00:20:47] 他会想什么 [00:20:48] 不同的人 [00:20:49] 他要的ABC [00:20:50] 他会去想不同的方向 [00:20:52] 我希望用最少的方式 [00:20:53] 把ABC呈现 [00:20:54] 全部呈现出来 [00:20:56] 所以你看就一个README [00:20:57] 你们都会用AI [00:20:58] 生成slop README [00:20:59] 但当你看过几个好的以后 [00:21:01] 你就会写README [00:21:05] 其实这我告诉大家 [00:21:06] 因为你有AI以后 [00:21:07] 你马上就可以问他 [00:21:08] 这个README为什么比那个 [00:21:10] 为什么 [00:21:10] 我看到这个项目的README [00:21:13] 没有AI Slop的感觉 [00:21:14] 他就会告诉你 [00:21:16] 所以这也是我理解的 [00:21:17] 为什么很多同学们怨气很大 [00:21:20] 觉得上课耽误学习 [00:21:22] 因为现在课外的信息量和信息密度 [00:21:26] 已经远远超过任何一个课堂 [00:21:28] 可以做的事情了 [00:21:30] 当你打开一个成熟的GitHub项目的时候 [00:21:34] 它是工程师十几年的心血 [00:21:37] 甚至在某一个时间点 [00:21:39] 十年以前 [00:21:39] 这个项目崩溃过一次 [00:21:41] 他做了一次大版本的重构 [00:21:43] 他发现以前的那个practice不够好 [00:21:46] 你才呈现到你面前的是一个 [00:21:48] 这个现在好好的东西 [00:21:51] 但是我们传统的教学方法 [00:21:53] 没有教过大家 [00:21:54] 怎么样把这些海量的信息密度 [00:21:57] 巨大的东西用好 [00:21:59] 然后大家也没有准备好 [00:22:01] 去拥抱这样的知识 [00:22:03] 就可能 [00:22:03] 可能为什么我现在在做老师 [00:22:05] 就是因为我有这种惊人的注意力 [00:22:08] 然后大家其实要训练的 [00:22:10] 也是那种惊人的注意力 [00:22:11] 你可以在看到一个东西的时候 [00:22:12] 快速的 [00:22:14] 有种什么感觉 [00:22:17] 教大家怎么学习 [00:22:19] 如果你有任何一个问题 [00:22:21] 你看到东西之前 [00:22:23] 我的大脑可能有一种能力吧 [00:22:26] 就会先把这个东西 [00:22:27] 我看到的所有东西都忽略掉 [00:22:29] 我会想如果我要自己来解决这个问题 [00:22:31] 我会怎么做 [00:22:32] 然后我会立即比较 [00:22:34] 我看到这个东西 [00:22:36] 和我脑袋里所想的东西 [00:22:38] 谁更好 [00:22:38] 然后如果我 [00:22:39] 我做得更好 [00:22:41] 就认为这是一个slop [00:22:42] 然后如果我发现 [00:22:43] 他做得比我好 [00:22:44] 我就很惊喜说 [00:22:45] 哎 这个 [00:22:46] 他为什么做得比我好 [00:22:48] 然后如果你养成了 [00:22:49] 这个习惯以后 [00:22:50] 这是一个非常恐怖的习惯 [00:22:52] 因为你在任何时候 [00:22:53] 都在疯狂地 [00:22:53] 从外面接收信息 [00:22:55] 然后你能留意到 [00:22:56] 生活里面 [00:22:56] 各种小细节的时候 [00:22:57] 你就不停地在 [00:22:58] 在这个提升 [00:23:00] 然后也有 [00:23:01] 其实很早的时候 [00:23:02] 大家就有推荐 [00:23:03] 如果你们要读一个 [00:23:04] research paper [00:23:05] 读一个研究论文 [00:23:06] 应该怎么读 [00:23:07] 我早前的时候展示AI是怎么样被注意力机制带到作者逻辑陷阱里面去了 [00:23:17] 他说有一个非常好的学习方法 [00:23:19] 这个学习方法其实也适用于大家学任何课程 [00:23:21] 包括你们的高等数学 [00:23:23] 包括你们的计算机系统基础 [00:23:24] 叫Virtual Implementation [00:23:26] 就是你现在有一个具体的问题或者具体的需求 [00:23:30] 你先在脑袋里面把这个问题自己做一遍 [00:23:34] 你会得到一个什么样的结果 [00:23:38] 如果你的方法和作者的方法是一致的 [00:23:40] 那很好说明你已经学会了 [00:23:42] 完全掌握这个知识了 [00:23:43] 然后如果你不一致 [00:23:45] 还有两种情况 [00:23:45] 一种是确实成熟的 [00:23:50] 好的教科书上的方案 [00:23:51] 比你想到的那个方案要好 [00:23:53] 要精巧 [00:23:53] 要简洁 [00:23:55] 你就会想为什么 [00:23:56] 为什么他能想到 [00:23:57] 他就变成你知识的一部分 [00:23:59] 还有一种情况 [00:24:01] 那就真的是惊喜中的惊喜 [00:24:02] 你的方法比他还要好 [00:24:05] 可能你一万次才会有一次这样的体验 [00:24:09] 但是这个体验给你的正反馈也很大 [00:24:11] 你突然会想 [00:24:12] 我能不能去做一个什么好玩的东西 [00:24:17] 所以我觉得在AI时代 [00:24:18] 你们更可以对细节保持注意力 [00:24:21] 比如说Git [00:24:22] 你看到各种各样的Git的repo [00:24:25] 你读别人的东西 [00:24:26] 甚至AI生成的东西 [00:24:27] 都有感知细节的方式 [00:24:32] 这是我开一个头 [00:24:35] 然后我们就可以讲 [00:24:36] 关于Git的一些具体的细节 [00:24:40] 就是首先 [00:24:42] 为什么要Git [00:24:44] 大家有没有想过 [00:24:45] 当然肯定 [00:24:46] 有GitHub以后 [00:24:47] 分发代码变得前所未有的方便 [00:24:50] 这件事情 [00:24:53] 可能他们创立GitHub的时候 [00:24:55] 就想到了 [00:24:57] 但他们没有意识到 [00:24:58] 这竟然成为了 [00:25:00] 比如说今天AI时代的 [00:25:01] 一个底层的技术设施 [00:25:02] npm [00:25:02] 你的所有的各种 [00:25:04] Neovim的配置 [00:25:05] 全部都在GitHub上 [00:25:07] 我就在上周末的时候 [00:25:09] 我参加了推免的面试 [00:25:11] 你们应该也在一年 [00:25:12] 或者两年以后 [00:25:13] 会来到这个场合去面试 [00:25:16] 然后我就问大家 [00:25:18] 什么是Git [00:25:19] 什么是Git [00:25:20] 那什么是Git [00:25:21] 大家多多少少都能说两句 [00:25:23] 我从GitHub里面 [00:25:24] 可以下载分发代码 [00:25:25] 然后有的同学 [00:25:26] 可以讲到Git管理的一些 [00:25:28] 项目目录的快照 [00:25:30] 然后挺好的 [00:25:32] 当然我觉得也没有必要 [00:25:34] 把所有人都挂掉 [00:25:36] 你能讲一些就可以 [00:25:38] 就取决于你能讲多少 [00:25:40] 来给你一个评估 [00:25:42] 然后Git Rebase是零off时 [00:25:45] 这个也意料之内 [00:25:49] OK [00:25:49] 但我比较意料之外的是 [00:25:52] 我问大家 [00:25:54] 如果Git Merge发生冲突以后 [00:25:56] 会观察到什么 [00:26:01] 你们有观察到过吗 [00:26:02] 就是如果你比如说你的作业老师发布了一个更新 [00:26:09] 然后但你本地已经把这个东西改的面目全非了 [00:26:12] 那这个时候你还是会让agent说来吧 [00:26:15] 然后agent会自动就啪啪啪啪啪啪 [00:26:18] 就给你修好了 [00:26:20] 反正说跟你说好了 [00:26:21] 然后我现在也是的 [00:26:22] 反正merge出错了 [00:26:25] 帮我修 [00:26:26] 然后反正最后他说好测试都通过了 [00:26:28] 然后我看一眼这个东西看起来OK [00:26:31] 这个时候惊人的注意力就有用了 [00:26:34] 如果你愿意在冲突的时候停下来 [00:26:36] 尤其是大家作为学生的时候 [00:26:38] 总是想我再学一点 [00:26:39] 反正这一秒钟 [00:26:40] 我学一下也没有坏处 [00:26:42] 你会看到一个非常神奇的 [00:26:44] 有好多等号 [00:26:46] 有好多箭头 [00:26:49] 然后在你们冲突以后 [00:26:51] 不是立即冲突就被resolve了 [00:26:55] 而是你这个责任就到了你一手 [00:26:58] 在曾经的时候 [00:26:59] 是到了每一个开发者手上 [00:27:01] 如果开发者发现了冲突 [00:27:02] 那开发者有义务要把这个冲突给解决掉 [00:27:05] 那他要解决掉 [00:27:07] 他就需要能看到这个冲突 [00:27:10] 他不能说 [00:27:10] 如果Git是设计成 [00:27:12] 你站在Git设计者的角度 [00:27:14] 如果两边 [00:27:16] 比如说我现在有一个文件 [00:27:18] 文件原来是1111 [00:27:20] 然后有一个人把第二行改成了2 [00:27:23] 有一个人把第二行改成了3 [00:27:25] 那这个到底是2还是3 [00:27:28] 我不能就Git说 [00:27:29] 对不起冲突了 [00:27:30] 我不能合并了 [00:27:31] 然后躺在那 [00:27:33] 那对人来说 [00:27:34] 这个负担是非常大的 [00:27:37] 那我到底这行应该是以二放进来 [00:27:39] 还是应该以三放进来 [00:27:41] 那我能不能 [00:27:43] 需求就是 [00:27:43] 你看所有的first principle都是连起来的 [00:27:46] 我刚才无意中举的那个地铁标识的例子 [00:27:51] Git也在用这个原则 [00:27:53] 就是如果你 [00:27:54] 如果你现在发生了一件事 [00:27:56] 那最好就是在这个地方 [00:27:58] 把它所有要的信息全部都放进来 [00:28:01] 所以你会回想到 [00:28:03] 你看到了 [00:28:03] 如果你以文本的方式来看 [00:28:05] 它有一个等于号 [00:28:06] 有好多尖括号 [00:28:07] 然后有些地方 [00:28:08] 有这种incoming的 [00:28:09] 这种head的这种标记 [00:28:10] 然后至少冲突的两部分代码 [00:28:13] 都同时显示在这里 [00:28:16] 然后如果你们用像IDE的话 [00:28:18] 你还可以看到 [00:28:19] 上面会多出一些按钮 [00:28:20] 叫什么accept incoming change [00:28:22] 你就可以在这个IDE里面 [00:28:24] 自己restore [00:28:24] 自己决定谁进谁出 [00:28:27] 完了以后 [00:28:28] 你就可以再冲进一个commit [00:28:30] 把它给resolve掉 [00:28:31] 你只需要一点注意力 [00:28:34] 其实你只要有一两次 [00:28:35] 你就会有一个印象 [00:28:38] 至少你能说出 [00:28:41] 两边冲突的代码 [00:28:43] 都会显示在一起 [00:28:45] 然后比较吃惊的是 [00:28:47] 大家都答得不好 [00:28:48] 那就肯定是 [00:28:49] 第一个没有用过 [00:28:49] 大家没有开发过 [00:28:53] 当然我觉得就算用agent的开发 [00:28:55] 你也很容易遇到这种conflict [00:28:56] 如果你让agent在不同分支开发 [00:28:59] 第二还是没有留意 [00:29:01] 所以比较重要的是 [00:29:03] 第一个去多用一用 [00:29:05] 第二个可以试图去拥有更好的注意力 [00:29:11] 好 那我们就来讲一讲Git是怎么来的 [00:29:14] 那肯定我们在U盘上面 [00:29:16] 放一个目录的方式是不行的 [00:29:17] 对吧 每次打一个包 [00:29:21] 但据我的观察 [00:29:23] 我在上学的时候 [00:29:24] 同学们确实是这样 [00:29:25] 当然我上学的时候可能比较早了 [00:29:27] 这个Git还没有get popular [00:29:29] 作业V1 [00:29:30] 作业V2 [00:29:31] 作业最终版 [00:29:33] 每次复制一个目录 [00:29:35] 没问题 [00:29:36] 这个是一个非常直觉的体验 [00:29:41] 这个方式可以找回旧的文件 [00:29:44] 因为你万一有一天手滑了 [00:29:46] 或者不小心改错了 [00:29:47] 尤其你们在新手阶段 [00:29:49] 不小心有一个误操作 [00:29:50] 是非常正常的 [00:29:52] 你想要回去上一个版本 [00:29:55] 看一看到底 [00:29:56] 我上一版本是怎么写的 [00:29:59] 这不就是一个需求吗 [00:30:02] 然后就从这样的一个需求 [00:30:04] 你自己就能发明一个 [00:30:06] 像Git这样的东西 [00:30:08] 因为本质上 [00:30:09] 所谓的版本管理 [00:30:11] 我们说什么是项目 [00:30:13] 项目就是一堆东西 [00:30:14] 你的代码文档 [00:30:16] 各种东西 [00:30:18] 测试用例全部都搅在一起 [00:30:20] 没问题 [00:30:21] 一个目录 [00:30:23] 然后你会想 [00:30:25] 如果我们能够 [00:30:27] 把目录的版本管理起来 [00:30:31] 如果我不再是叫作业一 [00:30:34] 作业二 [00:30:34] 作业三 [00:30:35] 我只有一个作业 [00:30:37] 一个作业的目录 [00:30:39] 但是我可以提供一个命令行工具 [00:30:41] 说我能不能看一看list [00:30:44] 作业的版本是什么 [00:30:46] 我能不能把当前的版本 [00:30:49] 变成另外一个版本 [00:30:50] 回到过去的一个版本 [00:30:52] 你不就发明了一个 [00:30:53] 虽然你还没有 [00:30:54] 还没有使用任何的什么分支 [00:30:57] 什么合并 [00:30:58] 冲突 [00:30:59] 这些概念 [00:31:01] 但是你不就已经发明了一个简化版的Git [00:31:03] 然后在你用着用着用着的时候 [00:31:05] 你自然而然的会去发明那些 [00:31:10] 比如说你要跟你的室友同步了 [00:31:12] 你就会出现 [00:31:13] 你们俩一起改同一个文件的情况 [00:31:15] 你就坐下来想 [00:31:17] 一起改了同一个文件应该怎么办 [00:31:19] 你就可以发明Git [00:31:21] 当然我觉得人类 [00:31:23] 所以这体现了人类的局限性 [00:31:27] 在Git之前 [00:31:30] 我们还用过一个 [00:31:31] 你们可能听都没有听过 [00:31:32] 叫SVN [00:31:34] SVN Check In Check Out [00:31:36] 然后SVN和Git [00:31:38] 它的设计就不一样 [00:31:40] SVN是这样的 [00:31:41] SVN说 [00:31:42] 我们存储的是项目的变化 [00:31:46] 假如你有一个最初的版本 [00:31:48] 然后每次你这个文件改了一行 [00:31:51] 我就存一个patch [00:31:52] 再改一行 [00:31:53] 存一个patch [00:31:55] 好像没有问题 [00:31:56] 这是一个非常高效的实现 [00:31:59] 是一个非常高效的实现 [00:32:00] 但是就是这个高效 [00:32:02] 随着项目的增长 [00:32:03] 把大家拖回去了 [00:32:06] 拖入了泥潭 [00:32:08] Git的模型要干净的多 [00:32:12] 就是大家first principle [00:32:13] 我想要先回到过去的某一个版本 [00:32:16] 马上一秒钟就给我回去 [00:32:18] 而你想如果你每次存的都是一个delta的话 [00:32:23] 你想要回到一个早期的版本 [00:32:25] 你就得一个一个往回退 [00:32:27] 当然你可以用一些缓存之类的方法来优化它 [00:32:34] 但这就我觉得有种好像确实是走错路了 [00:32:39] 所以Git基本上它就是这样来的 [00:32:42] 它的作者是Linux的作者Linus [00:32:46] 一个非常简单干净的模型 [00:32:49] 他们在1991到2002年的时候 [00:32:54] 如果想要维护Linux [00:32:56] 就是用的作业V1作业V2的方式 [00:32:59] 然后在邮件列表上面 [00:33:02] 这个发补丁 [00:33:04] 直接的邮件相当于是 [00:33:05] 我改了一个A.C [00:33:07] 然后我说我有一个A.C新版 [00:33:09] 然后我就在往邮件列表一发 [00:33:10] 然后会有一个maintainer [00:33:12] 会看到邮件列表1 [00:33:13] 这个把它放到他的这个核心 [00:33:16] 这个仓库里 [00:33:17] 然后再去release这个版本 [00:33:20] 然后直到后面他们还用一些其他的工具 [00:33:23] BitKeeper这样 [00:33:25] 然后直到Linus终于受不了了 [00:33:27] 他觉得我们就应该要一个绝对简单 [00:33:30] 但是又非常好用概念正确的版本控制工具 [00:33:34] 所以2005年的时候做了Git [00:33:36] 所以它比我们想象的年轻一点 [00:33:39] 2005年 [00:33:40] 你有兴趣的同学可以去看这个Pro Git [00:33:45] 当然直接让agent看就可以了 [00:33:47] 不需要再去读了 [00:33:48] 我读书的时候 [00:33:50] 我们都是看过这些文档 [00:33:54] 所以 [00:33:56] 我们需要项目级的时间回溯 [00:33:59] 我们都会犯错误 [00:34:00] 所以我们只要能去 [00:34:02] Git的历史里面 [00:34:03] 把它捞回来 [00:34:06] 就可以了 [00:34:07] 所以你们可以试着 [00:34:08] 用这种方式去 [00:34:11] 从已有的经验 [00:34:12] 你知道什么 [00:34:13] 我想要什么 [00:34:15] 然后可以推出下一个概念 [00:34:18] 它应该是什么样的 [00:34:19] 然后你发现吧 [00:34:20] 这就是数学的推理 [00:34:21] 我的 [00:34:22] 就我上课的时候 [00:34:23] 我这样讲下来的时候 [00:34:24] 我每一句话都是和之前是有逻辑关联的 [00:34:28] 然后这个逻辑关联 [00:34:29] 多多少少都有一些数学的联系 [00:34:31] 所以OpenAI认为 [00:34:32] 它要把数学先训好 [00:34:33] 训好以后 [00:34:34] 它所有的问题 [00:34:35] 它可能都能解决 [00:34:36] 然后这件事情可能就真的要能成真了 [00:34:41] 我自己是觉得品位上 [00:34:43] 可能评委可能也是能解决的 [00:34:48] 那我们还是讲一讲什么是什么是git [00:34:52] 讲一讲什么是git [00:34:54] 我还是 [00:34:57] 我还是建一个空的terminal [00:35:02] 那大家也都知道 [00:35:04] 你今天如果要开启一个项目的话 [00:35:07] 那就git init [00:35:12] 当然也不重要 [00:35:13] 它会告诉你一些奇怪的信息 [00:35:17] 那这个时候你就有两种选择 [00:35:20] 第一种是让agent来帮你做 [00:35:24] 帮你解决 [00:35:25] 帮你回答你不懂的问题 [00:35:28] 还有一种就是把它忽略掉 [00:35:29] 那显然是高效的学习方法是更好的 [00:35:34] 然后你得到了一个空的 [00:35:36] 就这么简单以后 [00:35:38] 你就得到了一个目录 [00:35:43] 一个.git目录 [00:35:45] 当然.git目录默认是不显示的 [00:35:47] 所以你看不到它 [00:35:48] 这就是全部了 [00:35:50] 当你做完git init以后 [00:35:51] 你的项目就变成了一个版本管理的 [00:35:54] 被Git版本管理的仓库 [00:35:58] 然后你可以从远处下载代码 [00:36:00] 可以把代码推到本地 [00:36:02] 然后你也可以 [00:36:03] 最基础的命令 [00:36:04] 比如说我有一个hello.c [00:36:07] 随便 [00:36:08] 你们喜欢写C [00:36:11] 那如果你有一个hello.c [00:36:12] 就可以git add [00:36:14] 比如说hello.c [00:36:16] 然后就可以看到 [00:36:17] 用git status可以看到 [00:36:19] 我现在的工作区里面有一个hello.c [00:36:27] 这里还有一些提示 [00:36:34] 这个很重要的是创建项目的快照 [00:36:37] 使得你可以在任意时间之间穿梭 [00:36:40] 所以我们创建快照的 [00:36:45] 我可以创建一个有消息的快照 [00:36:48] 这很重要 [00:36:50] 这体现了软件工程的智慧 [00:36:52] 你看之前我们用复制粘贴的方式 [00:36:56] 命名为项目版本V1 [00:37:00] 项目版本V2 [00:37:01] 项目版本V3 [00:37:02] 作业V1 作业V2 作业V3 [00:37:04] 然后Git的世界里面不是这样的 [00:37:08] 我看到很多同学在用Git的时候 [00:37:12] 把Git当成是作业V1 作业V2 作业V3来用的 [00:37:16] 但这个不是Git推荐的practice [00:37:18] Git推荐的practice是 [00:37:20] 或者说在长期的软件工程实践的经验当中 [00:37:24] 你每一次当对这个项目做了任何的改动以后 [00:37:29] 比如说改了一个文件 [00:37:30] 然后这个文件比如说修了一个bug [00:37:34] 或者给它增加了一个功能 [00:37:36] 或者调整了某一个脚本 [00:37:37] 或者调整了某一个文档 [00:37:38] 你都应该立即做一次git commit [00:37:42] git commit [00:37:43] 哎呀这怎么是nano [00:37:45] 然后你可以有一个文本 [00:37:47] 文本编辑器 [00:37:48] 说我这是做了什么 [00:37:55] 文本编辑器好 [00:37:56] 我可以保存 [00:37:59] 那这个提交就成功了 [00:38:03] 然后这些都 [00:38:03] 你们都可以在现有的 [00:38:07] 项目里面看到 [00:38:08] 他们是怎么样协作的 [00:38:09] 有些git 提交可能比较大 [00:38:11] 他们放了一个feature [00:38:12] 有些就比较小 [00:38:13] 他就fix非常少的东西 [00:38:20] 所以当你面对这样的一个新鲜的工具的时候 [00:38:25] 你去理解它的best practice [00:38:28] 还是需要花时间的 [00:38:29] 所以我还是觉得 [00:38:30] 你可能不能把所有的事情都交给AI [00:38:34] 你多多少少还会知道 [00:38:35] 我们为什么要做这件事 [00:38:37] 为什么 [00:38:39] 什么时候应该做git add [00:38:40] 什么时候应该做git commit [00:38:42] git commit的message应该怎么写 [00:38:46] 各种各样的 [00:38:47] 这样的知识 [00:38:53] 所以你真正需要的是 [00:38:57] 在任何时候学习 [00:38:59] 比如说碰到这个新东西的时候 [00:39:01] 追问一下 [00:39:02] 你刚才做的是不是符合best practice [00:39:05] 比如说我刚才做了一个git commit [00:39:10] 这个符合best practice吗 [00:39:15] 就是你问加上豆包输入法 [00:39:18] 真的就是一秒钟的时间 [00:39:22] 但是它会给你很多解释 [00:39:27] 它说这个commit不太符合best practice [00:39:29] 主要问题在提交信息 [00:39:32] 其次代码本身也有问题 [00:39:34] 提交信息的可读性有问题 [00:39:37] 我故意做了一个i [00:39:40] 完全看不出做了什么 [00:39:42] 所以Git首先 [00:39:44] 就它本质上 [00:39:46] 因为本质上 [00:39:46] 虽然它存储的是版本的快照 [00:39:49] 但是我们的项目本质上 [00:39:52] 还是从一个版本 [00:39:53] V1 [00:39:54] 然后经过一次修改变到V2 [00:39:57] 修改 [00:39:58] 那Git鼓励的是 [00:40:03] 你需要在message里面 [00:40:04] 描述出你改了什么 [00:40:07] 相当于你 [00:40:08] 你如果一直看 [00:40:12] 你就知道 [00:40:15] 项目是怎么发展 [00:40:16] 一直走到现在的 [00:40:18] 修了多少个bug [00:40:19] 加了什么需求 [00:40:20] 经历了什么样的冲动 [00:40:21] 你可以在message里面 [00:40:23] 看到这个项目的历史的轨迹 [00:40:25] 这是他鼓励的 [00:40:27] 然后这也是一个好的 [00:40:28] 软件工程的practice [00:40:29] 使得别人能够更容易的 [00:40:30] 理解你的项目 [00:40:32] 所以如果你的每一个提交 [00:40:33] 都是 [00:40:35] 你想 [00:40:36] 如果你有一万个提交以后 [00:40:38] 就会变成什么 [00:40:39] 这个commit message [00:40:40] 就变成slop了 [00:40:42] 如果一万个提交 [00:40:43] 都是I did some cool stuff [00:40:45] 那它 [00:40:46] 如果是这样 [00:40:47] commit message [00:40:48] 这个特性就不应该存在 [00:40:51] 你不要叫commit message [00:40:52] 你就从这个V1到V2就行了 [00:40:54] 然后你会在各种时候 [00:40:57] 用不同的commit message [00:40:58] 比如说 [00:40:59] 你这个版本要收尾了 [00:41:01] 那你可能会有一个版本 [00:41:02] 一个commit message会讲 [00:41:03] 我要Finalize这个版本 [00:41:06] 甚至git也允许你在不改任何代码的时候 [00:41:11] 就这个v和vr是完全相同的时候 [00:41:13] 再做一个提交 [00:41:15] 这件事情可以吗 [00:41:22] 你看 [00:41:26] 它拒绝提交你的空的 [00:41:31] 就没有修改的 [00:41:32] 但是agent可以 [00:41:38] 我让它做一个提交 [00:41:41] 然后要求他在不做这个的情况下提交 [00:41:50] 你看他知道他有知识 [00:41:53] 所以其实你们需要就是惊人的注意力 [00:41:56] 然后尝试问 [00:42:00] 从理论上说应该可以的 [00:42:01] 比如说我就要有一个 [00:42:03] 比如说我就说我要发布一个版本了 [00:42:05] 这是一个你就想 [00:42:06] 但这个不是best practice [00:42:07] 所以尽量不要做 [00:42:09] 你想要就要故意做一个这样的空的提交 [00:42:13] 然后确实看到了一个empty commit [00:42:19] 确实是空的 [00:42:22] 你看在什么时候有用 [00:42:24] 可以在无改动时产生一次提交 [00:42:28] 触发ci或者流水线 [00:42:29] 当我要有一个release [00:42:32] 当然其实release可以把tag打在这 [00:42:34] 但比如说这是一个bug fix [00:42:39] 在上一次的时候 [00:42:40] 我修完了最后一个bug [00:42:42] 然后我就得到了一个 [00:42:45] 我可以release的版本 [00:42:46] 当然git是可以在这放一个tag的 [00:42:48] 比如说v0.1 [00:42:50] 放一个tag说这是v0.1 [00:42:52] 但是我也可以强行做一个提交 [00:42:54] 说我要finalize [00:42:58] 这是没有改动的 [00:42:59] finalize就是说为了v0.1 freeze所有的东西 [00:43:02] 这就是一个没有改动的 [00:43:04] 我就把这种bug fix的提交 [00:43:06] 和finalize的提交给分开了 [00:43:08] 这件事情在偶尔的情况下 [00:43:11] 还是可以允许的 [00:43:13] 但你显然不能创建大量的这样的 [00:43:14] 这样的空的提交 [00:43:17] 所以你会看到 [00:43:19] 在Git里面 [00:43:21] 虽然它有模型 [00:43:23] OK你学了Git [00:43:25] Git push Git pull [00:43:27] 你就可以用Git了 [00:43:29] 都会用了 [00:43:30] 但其实真正水面下的冰山 [00:43:33] 是各种各样的best practice [00:43:36] 而这每一个best practice [00:43:37] 其实都是血和泪的教训 [00:43:41] 你反过来想 [00:43:42] 为什么message要这样写 [00:43:45] 你转手问AI [00:43:46] 你可以跟更有智力的模型讨论一下 [00:43:50] 这样做有什么好处 [00:43:52] 我觉得很多时候 [00:43:53] 模型它的表现比我好 [00:43:56] 就可能这个知识我是知道的 [00:43:59] 内化在我的脑子里面 [00:44:01] 然后你要给我 [00:44:03] 比如说给我两个小时的时间去想 [00:44:04] 我可能也能给你写一个非常好的 [00:44:06] 为什么要做这件事 [00:44:07] 但是我的智力是有限的 [00:44:10] 我的精力是有限的 [00:44:11] 我的注意力也是有限的 [00:44:13] 比如说我现在在上课的时候 [00:44:14] 我为了专注的和大家上课 [00:44:17] 我大脑里面做思考的那部分 [00:44:19] 其实我就有点不够了 [00:44:21] 我容量都有点不够了 [00:44:23] 所以我很多时候 [00:44:25] 我觉得我上课 [00:44:25] 讲授一个概念的时候 [00:44:26] 我讲的是suboptimal的 [00:44:27] 我讲的不是最好的 [00:44:31] 如果你回头把这些问题 [00:44:32] 比如说为什么commit message [00:44:33] 要这样写 [00:44:34] 你去跟比如说ChatGPT聊 [00:44:37] 就像我刚开始那个说 [00:44:41] 我求助一个我无法掌控的东西 [00:44:44] 但他知道这个世界上所有的 [00:44:46] 知道世界上所有的东西 [00:44:48] 他能很好的把这些概念给你讲清楚 [00:44:54] 然后这些约定俗成的best practice [00:44:56] 比如说 [00:44:58] 我来做一点不是best practice的事情 [00:45:02] 比如说 [00:45:04] 这是你们非常常见的事情 [00:45:09] 这个应该是可以编译的 [00:45:13] 这时候你有一个untracked的file [00:45:16] 然后你们在tutorial里面看到 [00:45:20] 有一个命令很好 [00:45:21] 叫git add . [00:45:23] 这样你如果有好多好多的文件 [00:45:26] 比如改了三五个文件 [00:45:27] 你不需要git add a.c b.c c.c了 [00:45:29] 你就git add . [00:45:30] 然后你就把它提交上去了 [00:45:37] git是不阻止你做这件事情的 [00:45:41] 这是完全合法的 [00:45:42] 然后那我再问 [00:45:50] 那我就问 [00:45:51] Git提交有什么不符合Best Practice的吗 [00:45:54] 他会看一看 [00:45:56] 按严重程度排序 [00:45:58] 那还是很聪明的 [00:46:00] 提交信息毫无信息量 [00:46:04] 他已经 [00:46:05] 他开骂了主观废话 [00:46:09] 这就比较好 [00:46:12] 他还会告诉你应该怎么做 [00:46:16] 用祈使句 [00:46:17] 现在时描述变更动机 [00:46:19] 例如fix off by one [00:46:21] in strlencheck [00:46:23] 你的c里面有这种 [00:46:24] 不好的practice [00:46:25] 我现在fix了 [00:46:27] 然后常用的约定 [00:46:28] Conventional Commits [00:46:29] 以前缀也值得考虑 [00:46:31] 当然这个就是 [00:46:31] 你可以在更多的项目里面 [00:46:33] 看到这样的commit的方式 [00:46:35] 好 第二个 [00:46:36] 提交粒度和内容混杂 [00:46:40] 他开始批评我了 [00:46:42] 这个还是你看 [00:46:44] 这是首要的 [00:46:45] 提交信息毫无信息 [00:46:47] 是最重要的问题 [00:46:48] 然后提交粒度 [00:46:51] hello是编译产物 [00:46:53] 根本不应该进版本库 [00:46:55] 应该加进gitignore [00:46:57] 用构建系统产出 [00:47:01] 所以git的使用方式 [00:47:03] 和你们的这种 [00:47:06] 作业v1作业v2还是不一样的 [00:47:09] 因为你的作业v1里面 [00:47:11] 那个exe一定在里面 [00:47:12] 作业v2你的exe也在里面 [00:47:14] 但是git [00:47:15] 它为了防止版本库的膨胀 [00:47:17] 尤其是如果你这个项目里面 [00:47:19] 有一万个文件 [00:47:21] 有一百个合作者的时候 [00:47:23] 每一个合作者的环境 [00:47:25] 都是不一样的 [00:47:27] 你想每个环境都是不一样 [00:47:28] 他们build出来的东西 [00:47:30] 也是不一样的 [00:47:31] 你们可能不知道 [00:47:32] 你们的编译 [00:47:35] 每一次那个文件 [00:47:37] 都可能是 [00:47:38] 比如说带一个日期的 [00:47:40] 它甚至带hash的 [00:47:41] 带你本机的指纹的 [00:47:44] 它每一次的这个二进制文件 [00:47:45] 可能都是不一样的 [00:47:46] 然后如果你把这个东西放进去了 [00:47:48] 下一次就会产生冲突 [00:47:50] 因为你这里有个hello [00:47:53] 对方有个hello [00:47:53] 哪怕是一样的源代码编译出来 [00:47:55] 它都可能不一样 [00:47:56] 更何况如果是不一样的源代码编译出来的hello [00:47:59] 然后这个就会产生一个二进制的冲突 [00:48:02] 而二进制的冲突是很难合并的 [00:48:05] 你想如果我编译出来的东西 [00:48:07] 不是这种程序编译出来的 [00:48:09] 如果我是生成的图片 [00:48:12] 当然图片是一个比较特殊的东西 [00:48:16] 图片怎么合并 [00:48:18] 合并就是有重复的 [00:48:21] 所以这里面有很多很多的best practice [00:48:26] 然后我看一下它 [00:48:30] 它可能会出乎意料 [00:48:32] 我不知道它会发生什么 [00:48:37] 似乎网断了 [00:48:40] 没有 [00:48:41] 它还花了一些比较长的时间来思考 [00:48:45] 网没有断开 [00:48:52] 他说 [00:48:55] 丢弃空提交和编译产物两端历史 [00:48:58] 新建Main分支 [00:48:59] 重写为两个有意义单一值的提交 [00:49:08] 他还贴心的发现历史没有remote [00:49:11] 也就是说我的仓库没有和另外一边同步 [00:49:14] 不会有别人 [00:49:15] 因为如果别人也持有的话 [00:49:16] 那就是一个比较糟糕的事情了 [00:49:19] 没有别人 [00:49:20] 所以他把整个这个项目就给改了 [00:49:26] 你看 [00:49:28] 如果你的注意力注意到 [00:49:30] 你发现它换了一套 [00:49:33] 这个commit的方式 [00:49:35] 然后它会有一个这样的 [00:49:38] headline [00:49:39] headline的message [00:49:40] 然后在headline的message的后面 [00:49:42] 还有一些 [00:49:43] 然后这里面还有好多好玩的 [00:49:45] 比如说 [00:49:45] 如果你在这里 [00:49:47] 有一个什么 [00:49:48] 写一个什么fixes #3 [00:49:49] 比如说你在这个message [00:49:51] 写一个fixes #3 [00:49:53] 然后GitHub会自动 [00:49:55] 把你的这个issue给close了 [00:49:58] 这里面有很多很多好玩的 [00:50:00] 你只有反复的使用 [00:50:02] 你才能获得一个 [00:50:07] 就是初学者和有一定经验的人的区别 [00:50:11] 对 当然 [00:50:13] 现在是agent时代 [00:50:15] 所以你们都是agent native的人 [00:50:17] 你们肯定也不会用古法 [00:50:19] 就我觉得是 [00:50:22] 这种什么git status [00:50:25] 这种都已经成为了古代手艺了 [00:50:29] 你们现在一定是用agent [00:50:31] 说就你不需要知道 [00:50:34] 我具体的命令是什么 [00:50:36] 但是你只要有概念 [00:50:38] 说我的git能干什么 [00:50:40] 你就可以让他用符合best practice的时候干 [00:50:43] 当然这带来一个问题是 [00:50:44] 你其实根本就分不清 [00:50:45] 你是真的就你自己能干 [00:50:48] 还是你托业的agent就干不了 [00:50:50] 但也许我觉得这也不重要 [00:50:52] 所以为了增加大家的学习的效率 [00:50:58] 其实可以这样 [00:51:02] 在做任何git操作之前 [00:51:03] 都要逐个检查文件的变化 [00:51:06] 比如说 [00:51:08] 比如说 [00:51:08] 我来随便试一个 [00:51:11] 是一个不能编译的代码 [00:51:17] 这个会编译 [00:51:18] 不能编译 [00:51:23] 我让它做一个提交 [00:51:28] 我也不知道模型会给我什么 [00:51:30] 你看他很聪明 [00:51:34] 他很聪明 [00:51:34] 他比你聪明 [00:51:36] 你在改作业的时候 [00:51:37] 我觉得我们都会有这种倾向 [00:51:39] 就改一个字不要紧的 [00:51:42] 就改了就提交吧 [00:51:43] 你一定会这样想的 [00:51:44] 但是模型有工程 [00:51:48] 他见过很多好的工程项目 [00:51:50] 也读过很多软件工程的书 [00:51:52] 所以他知道 [00:51:54] 当前不建议直接提交 [00:51:56] 他还给你修好了 [00:51:59] 他给你解释 [00:52:00] 可能有几个prompt [00:52:02] 比如说我有一个 [00:52:04] 我的有一个system prompt [00:52:05] 大概可能比如说中文 [00:52:07] 或者我在上课 [00:52:09] 所以你可能要讲的更详细一点 [00:52:11] 这样的prompt [00:52:12] 然后以及 [00:52:15] 他给了很多很好的解释 [00:52:17] 为什么他不符合 [00:52:18] git提交应当是一个完整自洽的快照 [00:52:22] 提交编译不过的代码 [00:52:24] 相当于让仓库处于坏状态 [00:52:27] 而且你看他还有更多的 [00:52:29] 这是我都没有想到 [00:52:30] 这就是我知道 [00:52:31] 但是我在上课的时候 [00:52:33] 即兴的时候 [00:52:33] 我是想不到它的 [00:52:35] 如果真的有人要去这个git bisect [00:52:38] 还有git还有一个很有意思的功能 [00:52:40] 叫git blame [00:52:41] 我很喜欢这个功能 [00:52:45] 比如说hello.c [00:52:47] 然后它会告诉你 [00:52:49] 每一行代码 [00:52:51] 都是在哪一次提交 [00:52:52] 哪一个地方改的 [00:52:55] 也就是说当我有一个 [00:52:56] 就哪怕是一个线性的 [00:52:57] 这样的历史的时候 [00:52:59] 我都可以干很多有趣的事情 [00:53:03] 你看一下子 [00:53:04] AI agent的一句话 [00:53:05] 就把Git给打开了 [00:53:08] 我们可以做二分查找 [00:53:10] 大家都知道二分查找吗 [00:53:11] 你们可能会在保研的时候 [00:53:13] 或者工作面试的时候 [00:53:15] 去要手写一个二分查找 [00:53:18] 二分查找是说 [00:53:19] 如果你有一个很长的数组 [00:53:21] 你也可以把Git提交的历史 [00:53:24] 想象成是一个很长的数组 [00:53:25] 然后你现在在当前的版本上 [00:53:30] 发现一个bug [00:53:32] 然后但这个bug可能不是由我的代码引起的 [00:53:34] 可能是过去的某一个地方引起的 [00:53:36] 你这时候有一个测试用例 [00:53:38] 或者有一个任何一个能够检查出这个bug的东西 [00:53:41] 你就可以对整个的Git的历史记录做二分查找 [00:53:44] 因为本质上这个Git的历史是有一些是 [00:53:49] 有一些提交是OK的 [00:53:51] 有一些提交是不OK的 [00:53:52] 然后应该是在某一刻引入了一个bug以后 [00:53:57] 这个从某一个时刻开始 [00:53:59] 这些提交就都不能通过了 [00:54:01] 但是在这个之前的 [00:54:04] 这个每一个提交应该都是OK的 [00:54:06] 所以这不就是一个在 [00:54:09] 所有的前缀都是0 [00:54:10] 后缀都是1的数字里面找第一个1吗 [00:54:14] 这是二分查找能找的事情 [00:54:16] 你可以做二分 [00:54:19] 然后你还可以git blame [00:54:21] 你可以知道是谁 [00:54:22] 在你知道错了以后 [00:54:25] 知道错了以后 [00:54:26] 你可以知道是谁改了这一行代码 [00:54:29] 然后发生了什么 [00:54:33] 然后它有很多软件工程的实践 [00:54:38] 所以软件工程 [00:54:40] 我一直觉得可能不需要在课上讲那么多的东西 [00:54:44] 你要把时间留给你们 [00:54:46] 你们自己在实践当中 [00:54:48] 由agent来告诉你 [00:54:50] 每次提交的代码 [00:54:52] 应该能够通过构建和测试 [00:54:53] 这是这其实是一个非常基础的要求 [00:54:57] 大家也会觉得OK [00:54:58] 应该是这样 [00:55:00] 但是但你反复的强化潜移默化以后就OK了 [00:55:04] 然后他又说AGENTS.md是可以可以提交 [00:55:09] 这是符合这个best practice的 [00:55:10] 然后我就不不在这个不再做了 [00:55:14] 所以好好消息是 [00:55:16] 我以前在LLM之前 [00:55:20] 目前一般来说有新同学进组 [00:55:23] 都会有一些高血压时刻 [00:55:26] 就是使用Git的best practice [00:55:27] 因为有时候我们要做一个研究项目 [00:55:29] 欢迎你加入这个研究项目 [00:55:31] 你去开一个Git repo吧 [00:55:34] 然后你大概你第一周 [00:55:36] 可能会有一个非常简单的任务 [00:55:37] 打一个架子 [00:55:38] 然后把一两个东西跑起来 [00:55:40] 然后一般来说 [00:55:42] 我都会在打开Git项目以后 [00:55:45] 进入一个高血压环节 [00:55:47] 比如说 [00:55:48] 我一看到比如Dist或者Log会被交上来 [00:55:53] Log也是生成的文件 [00:55:55] 所以它不应该交 [00:55:57] 然后个人的配置 [00:55:58] 比如说VSCode你个人的设置 [00:56:00] 然后甚至VSCode里面 [00:56:02] 会有你的什么C盘下的什么东西的 [00:56:06] 那样的一个硬编码的路径 [00:56:07] 显然在任何一个其他地方 [00:56:08] 都是没有办法复现的 [00:56:11] 然后当然更高起来的是 [00:56:14] 我可以在审稿的时候 [00:56:15] 看到一些可以处于active状态的API key [00:56:19] 然后当我发现这个API key是DeepSeek的时候 [00:56:22] 我就知道这应该是一个中国兄弟 [00:56:26] 然后以及你们用Mac的同学 [00:56:28] 不经意之间会提交上来这个.DS_Store [00:56:32] 这是Git的一个特性 [00:56:34] 这是没有ignore [00:56:38] 然后我可以 [00:56:41] 在苹果系统上 [00:56:43] 你会就是如果你用Finder去打开一个目录 [00:56:48] 它会自动留下那个一些索引 [00:56:50] 大概是你把哪些子目录展开了 [00:56:52] 这些放在一个叫.DS_Store的文件里 [00:56:56] 但这个文件因为是点开头的 [00:56:57] 所以它是默认是隐藏的 [00:57:00] 你看我的LS还很贴心的 [00:57:03] 给它一个苹果的图标 [00:57:06] 苹果图标 [00:57:07] 所以你是看不到的 [00:57:08] 然后你看不到的时候 [00:57:09] 你会git add点 [00:57:11] 然后git add点就糟糕了 [00:57:13] 你看这个.DS_Store会被git追踪 [00:57:17] 当然这也是合理的 [00:57:19] 因为点有可能是一些 [00:57:20] 比如说点配置文件 [00:57:22] 点github [00:57:23] workflow这些约定俗成的东西 [00:57:25] 所以它默认带点的都会放进来 [00:57:28] 但这个临时文件就有一点糟糕 [00:57:31] 因为你可能会经常的变 [00:57:33] 然后它就不应该被提交进来 [00:57:36] 但又有一些 [00:57:37] 这个规则又比较复杂 [00:57:39] 比如说不是所有的generated file [00:57:42] 都不应该提交 [00:57:44] 比如说你可能有个package lock [00:57:46] 然后你可能会generate一个header [00:57:48] 然后这个header确实是 [00:57:50] 确实是可以generate [00:57:51] 但是你把它放进来 [00:57:52] 就可以方便编译 [00:57:54] 就别人可能在不需要下载 [00:57:56] 很多依赖的情况下 [00:57:57] 依然可以编译这个项目 [00:57:59] 你可能用另外一套工具链 [00:58:00] 去生成了这个header [00:58:01] 然后这个header [00:58:04] 但是你可以在一个 [00:58:05] 没有这个工具链的 [00:58:06] 另外一个地方去编译 [00:58:08] 所以也没有绝对的规则 [00:58:11] 我们看过很多软件 [00:58:12] 因为我自己是做软件工程研究的 [00:58:15] 一个非常标准的软件工程研究 [00:58:17] 就是比如说去研究你的Git的这个项目 [00:58:21] 满不满足这些Best Practice [00:58:22] 然后然而在不满足的时候 [00:58:24] 给你一个Warning [00:58:25] 那比如说像.DS_Store这种东西 [00:58:27] 它就需要一个规则 [00:58:29] 然后好多好多规则 [00:58:32] 然后最后做一个小工具 [00:58:33] 在Git提交之前告诉你OK不OK [00:58:36] 然后但所有这些东西都被大模型杀死了 [00:58:38] 因为大模型可以如此耐心的 [00:58:41] 给你检查 [00:58:42] 还给你一个emoji [00:58:43] 让你开心一下 [00:58:45] 缓和你的情绪 [00:58:46] 有的时候就比如说 [00:58:50] 我作为导师 [00:58:51] 我今天把学生叫到办公室 [00:58:53] 然后说 [00:58:54] 这个肯定第一个 [00:58:56] 我肯定高血压了 [00:58:57] 第二个我还得装作nice一点 [00:59:00] 我会说好 [00:59:01] 你提交的这个项目 [00:59:03] 不太符合软件工程的实践 [00:59:04] 然后这样这样这样 [00:59:06] 然后学生在听的时候 [00:59:08] 他也觉得我不满意 [00:59:10] 就不管怎么样 [00:59:10] 我在纠正他 [00:59:11] 就无论我的情绪多么正常 [00:59:14] 我只是告诉他一个 [00:59:17] 我教授他一个知识 [00:59:18] 这个时候学生总是有压力的 [00:59:20] 毕竟是老板批评我了 [00:59:22] 这个我做了一件我不该做的事 [00:59:23] 然后这个suppose可能是应该知道的 [00:59:26] 所以AI你看真的解放了 [00:59:28] 这个同学们 [00:59:31] 那个你无法控制的力量 [00:59:34] OK [00:59:36] 而且真的 [00:59:37] 我觉得AI很强 [00:59:38] 我最近在一个更严肃的 [00:59:40] 就一个更严肃的项目 [00:59:42] 我希望把每一个细节都做好的时候 [00:59:45] 我面临一个选择 [00:59:47] 大概是我们在Git提交之前 [00:59:52] 我们有一个技术叫Hook [00:59:55] 大家用过吗 [00:59:57] 各种各样的Hook 钩子 [00:59:58] 你可以Hook程序的一个函数调用 [01:00:00] 然后在这个函数调用结束的时候 [01:00:02] 干一件事 [01:00:02] 你也可以在你的agent里面加入一个Hook [01:00:05] 在某件事发生的时候 [01:00:08] 干一件事 [01:00:08] 比如说我有一个Hook [01:00:10] 会在agent完成需要我输入的时候 [01:00:14] 它会播放一个声音 [01:00:16] 叮一声 [01:00:17] 然后你现在也可以直接 [01:00:19] 就是你可以直接让它说什么 [01:00:24] 帮我配置一个全局的hook [01:00:28] 在agent需要输入的时候 [01:00:32] 叮一声提醒 [01:00:36] 完全没问题 [01:00:37] 你在任何一个Claude Code [01:00:39] 你的codex [01:00:40] 你的派 [01:00:41] 它都支持这样的钩子 [01:00:43] 然后我可以在git的pre-commit hook上面 [01:00:46] 做一些检查 [01:00:48] 因为可能这个项目的工程标准会更高一些 [01:00:53] 那么我就面临一个 [01:00:55] 我应该选上面还是应该选下面 [01:00:58] 也就是说我要给我的各种规则 [01:01:00] 可能我有几十个这样的checking rules [01:01:03] 然后我要给这个规则命名 [01:01:05] 那我应该选这个大写还是小写 [01:01:09] 然后首先从使用的角度来看 [01:01:12] 都没有问题 [01:01:12] 就是两个都是好的 [01:01:13] 只要我这里没有type [01:01:15] 我这句话里没有type [01:01:16] 就是OK的 [01:01:18] 但到底是大写还是小写 [01:01:22] 两难的问题 [01:01:23] 然后你在学习和生活的过程中 [01:01:28] 你又面临无穷多这样两难的问题 [01:01:30] 如果你的每一个问题都稍微问一句 [01:01:37] 你就会有很多很多的长进和知识 [01:01:41] 我看一下 [01:01:45] 他在处理了 [01:01:56] 他说好了 [01:02:02] 有声音吗 [01:02:06] 没有 [01:02:09] 我好像听到声音了 [01:02:11] 很轻的一声 [01:02:13] 再来一次 [01:02:21] 没有 [01:02:23] 但你看他意识到 [01:02:27] 意识到我的意图了 [01:02:30] 然后你让他修就行了 [01:02:31] 我就不让他修了 [01:02:33] 在这里面 [01:02:36] AI给了 [01:02:37] 我让GPT解释一下 [01:02:39] 然后你看它 [01:02:41] 这个TypoCheck [01:02:42] 它说它是一个固定规则的标识 [01:02:46] 然后它还从这个RFC里面 [01:02:48] 给了一些引用 [01:02:52] 它分析我的心理 [01:02:54] 为什么我会觉得这个全大写会更强一点 [01:02:56] 然后如果你们能在学习的过程中 [01:02:59] 比如在学习Git的过程中 [01:03:00] 多问几句 [01:03:00] 把每一个细节都做好 [01:03:02] 那你们一定会成为这个 [01:03:04] a lot better person [01:03:11] 行 [01:03:11] 那么接下来你就要开始学习Git了 [01:03:14] 首先你AGENTS.md也有了 [01:03:18] AGENTS.md也有了 [01:03:20] 那么接下来你就是要想要学一学 [01:03:24] Git到底是怎么工作的 [01:03:27] Git的底层原理 [01:03:28] 当然我可能会推荐大家去看 [01:03:30] 我在计算机系统课上面 [01:03:32] 讲Git的那一段 [01:03:34] 我就没有必要再讲一遍了 [01:03:38] 我就讲一个头 [01:03:40] 讲一个头是什么 [01:03:42] 是Git是一个持久化的数据结构 [01:03:47] 所以它就是一个数据结构 [01:03:49] 那Git持久化的是什么 [01:03:51] 目录树 [01:03:53] 因为我们说就是快照不停的版本 [01:03:55] 版本一版本二版本三版本四 [01:03:57] 那你就从这个最基础的想法出发 [01:04:00] 你就知道如果我要存一个目录树 [01:04:03] 比如说杠 [01:04:05] 比如说hi [01:04:07] 比如说a.txt [01:04:09] 那么b.txt [01:04:11] 还有一个b.txt [01:04:14] 那有可能是在这里面还有一个 [01:04:19] 比如还有一个目录叫d [01:04:20] 然后d里面有无穷多个文件 [01:04:24] 这个太多了 [01:04:25] 这个doesn't matter [01:04:28] 那我现在有一个很大 [01:04:29] 相当于我有一个非常大的项目树 [01:04:32] 那我今天 [01:04:33] 这个已经快照了 [01:04:34] 比如说这就是 [01:04:35] 这就是一个V [01:04:37] 我现在要创建V2 [01:04:41] 我要创建V2 [01:04:43] 我需要把这个 [01:04:44] 所有的文件都复制一份吗 [01:04:47] 我不需要 [01:04:49] 比如说我现在改的是B.txt [01:04:51] 我现在这个B.txt改了 [01:04:53] 那我肯定是需要 [01:04:55] 我需要再创建一个b.txt的新版本 [01:05:01] 新版本 [01:05:03] 那如果我想要以最小的代价 [01:05:06] 记下一整棵树 [01:05:08] 我应该怎么办 [01:05:11] 我应该把它到根的路线路径 [01:05:14] 反正我要 [01:05:17] 我要再复制一份 [01:05:19] 但是 [01:05:20] 根里面的这个a.txt [01:05:22] 我就可以用一个指针指过来 [01:05:25] a.这个根下的hi [01:05:27] 我不能指向这 [01:05:27] 因为指向这就意味着 [01:05:28] 我要用旧的B点前期 [01:05:30] 所以它到根的路径都要复制一份 [01:05:32] 那我有一个指针指到这里 [01:05:34] 然后以及我的这个地 [01:05:35] 我也可以有一个指针指向这里 [01:05:38] 虽然不太好看 [01:05:40] 但是这就是V2 [01:05:43] 所以即便你的项目很大 [01:05:46] 里面有一百万个文件 [01:05:48] 你只要 [01:05:50] 首先目录本身就不可能很深 [01:05:52] 没有人会去想建一个 [01:05:54] 一千层深的目录 [01:05:55] 你也没有必要 [01:05:56] 从这个渐进式披露的角度 [01:05:58] 你没有必要去建一个巨大无比的目录 [01:06:00] 它只要 [01:06:02] 你每一级索引都是指数级的增加 [01:06:05] 所以你很快就能把所有的类给分完 [01:06:08] 那么你只需要创建一个非常小的增量 [01:06:13] 你就得到了一个版本的快照 [01:06:16] 所以在Git里面 [01:06:18] 在Git里面 [01:06:19] 它底层 [01:06:20] 大家看到点Git [01:06:27] 在点Git里面 [01:06:29] 有各种各样的objects [01:06:32] 有各种各样的objects [01:06:33] 然后这个objects [01:06:36] 它就是一个普通的文件 [01:06:37] 你看是带hash索引的 [01:06:39] 然后就是这种节点 [01:06:41] 各种节点 [01:06:42] 目录的节点 [01:06:43] 文件的节点 [01:06:44] 然后甚至有committed的节点 [01:06:46] commit object [01:06:48] 它有commit object [01:06:50] 然后它有tree object [01:06:52] 然后它还有blob object [01:06:55] 三大类型的对象 [01:06:58] blob就是binary [01:06:59] 就是字节的序列 [01:07:02] 然后它就是一个数据结构 [01:07:03] 然后这个数据结构可以不断的向后升高 [01:07:06] 向后升高 [01:07:07] 向后升高 [01:07:07] 然后这个解决问题了 [01:07:09] 其实就解决问题了 [01:07:10] 所以你们如果从first principle [01:07:12] 这一个大学生想设计出一个这样的东西 [01:07:15] 是完全有可能的 [01:07:18] 对 完全有可能的 [01:07:20] 好 下一个 [01:07:24] 你能不能 [01:07:26] 这是纸面上的讲解 [01:07:28] 我讲完以后你有没有觉得有点不踏实 [01:07:33] 我们的git真的是这样实现的吗 [01:07:36] 老师上课说这个commit object [01:07:37] tree object [01:07:38] blob object [01:07:39] 真的有这样吗 [01:07:41] 然后如果发生分叉 [01:07:43] 首先因为我可以这样创建快照 [01:07:47] 我也可以从这个v1版本 [01:07:48] 创建一个另外的一个tree [01:07:50] 比如说到这一个tree [01:07:53] v1的一撇 [01:07:55] 然后我的这个世界就分叉了 [01:07:57] 分叉以后合并到底会发生什么 [01:08:01] 你就自然而然的想 [01:08:04] 想像这些问题 [01:08:05] 那么根据计算机科学定律 [01:08:10] 你想要的东西就一定有人做 [01:08:13] 所以 [01:08:17] 所以 [01:08:18] 所以我开一个浏览器 [01:08:22] 我以前上课的时候也会用这个 [01:08:41] 他是就我相信这是就是就是古法 [01:08:43] 这古法编程的巅峰之作之一了 [01:08:47] 它以可视化的方式展示了 [01:08:53] 展示了这个git是怎么工作的 [01:08:57] 比如说它甚至模拟了各种各样的命令 [01:09:02] 你看git status它都有 [01:09:04] 现在有一个改了的 [01:09:07] 它不需要改 [01:09:11] git commit [01:09:15] 看到右边动画了吗 [01:09:18] 它就多出了一个版本 [01:09:20] 然后你看它可以git checkout [01:09:22] 我点这个版本的时候 [01:09:26] 他可以看到版本的变化 [01:09:29] 他用这个方式来教你 [01:09:31] 这个git是怎么工作的 [01:09:33] 然后到今天 [01:09:39] 到今天 [01:09:41] 我觉得就可以直接 [01:09:45] vibe coding这些东西了 [01:09:46] 虽然虽然 [01:09:47] 可能vibe coding的质量 [01:09:50] 还并没有那么好 [01:09:56] 我做了一个 [01:09:57] 我直接vibe coding了一个 [01:09:59] 一个可视化整个Git repo的工具 [01:10:12] 你看它甚至还有一些 [01:10:13] 这个未提交的代码 [01:10:17] 你看GPT-6虽然它有视觉能力 [01:10:19] 但是一遍直出 [01:10:21] 还是产生了一些slop的 [01:10:26] 我给它这样的一个提示词 [01:10:28] 实现一个Git V命令行工具 [01:10:31] 把一个真实的Git repo [01:10:34] 你可以使用Git命令 [01:10:36] 然后把里面主要概念彻底完整的可视化 [01:10:40] 展示任何对象之前都从文件系统里的真实文件 [01:10:43] 注意 [01:10:44] 这是和刚才你看到那个 [01:10:46] learngitbranching.js.org不一样的地方 [01:10:49] 它是模拟出来的 [01:10:50] 它建了一个Git的模型 [01:10:52] 但是你今天你是有办法在真正的 [01:10:56] 就在live的这个Git repo上面 [01:11:00] 直接看到它发生什么 [01:11:01] 我从真实的文件原始的byte出发 [01:11:03] 展示一步一步如何解析成了什么 [01:11:06] 然后我要求它可视化 [01:11:09] 并且我让它持续监听目录中Git的变化 [01:11:12] 然后再发生变化后 [01:11:14] 用动画效果来展示Repo发生的变化 [01:11:17] 一会儿 [01:11:36] 我先来把它删掉 [01:11:38] 你看 [01:11:38] 我直接删除 [01:11:46] 你看右边的文件没有了 [01:11:49] 看吧 [01:11:49] 右边的文件没有了 [01:11:51] 然后我们来看一下这棵树 [01:11:53] 这是Main分支 [01:11:56] 它就像我画的这个一样 [01:11:59] 你看最早的一次提交 [01:12:02] 是一个I did some cool stuff [01:12:03] 里面有一个空的hello.c [01:12:06] 空的hello.c [01:12:07] 然后这个空的hello.c [01:12:08] 你看到它的这个文件 [01:12:11] 是来自于-temp下的hello下的一个object [01:12:14] 这真的是一个blob object [01:12:16] 然后你能看到它的字节序列 [01:12:19] 看到这个字节序列以后 [01:12:21] 它会帮你解压缩 [01:12:23] 然后你发现 [01:12:23] 对于很小的文件来说 [01:12:25] 这个压缩反而是浪费 [01:12:26] 它31字节解压出了一个23字节的东西 [01:12:31] 但这不要紧 [01:12:31] 这是一个blob [01:12:33] 你看blob15 [01:12:34] 这个文本 [01:12:35] 这是一个blob object [01:12:36] 大小是15 [01:12:38] 然后int main(void) [01:12:41] 你就可以看到这个 [01:12:44] 然后除了这样的blob以外 [01:12:46] 还有 [01:12:48] 这个hello的一个二进制文件 [01:12:51] 你看blob158 [01:12:53] 这是7f12f [01:12:54] 一个可执行文件的头 [01:12:57] 然后你也能够看到它 [01:12:59] 以及它也有tree object [01:13:03] 然后tree object [01:13:04] 同样你也可以看到 [01:13:05] 它的磁盘上面的原始的数据 [01:13:08] 52字节解压出43字节 [01:13:11] 然后你看这个tree是这样的 [01:13:14] 644 [01:13:15] 644代表这个文件访问的权限 [01:13:17] 然后它有hello.c [01:13:18] 真的是一个 [01:13:19] 你看以文本的形式 [01:13:20] 解压完了以后 [01:13:21] 就以文本的形式存储了hello.c [01:13:25] 然后你看 [01:13:25] 它的树的结构 [01:13:28] Bytes [01:13:29] Object [01:13:31] Mode644 [01:13:32] NameHello.c [01:13:35] TreeObject [01:13:36] 然后这个TreeObject指向了 [01:13:40] 看到这个杠 [01:13:41] 在这个版本里面的这个杠 [01:13:43] 它就有两个文件 [01:13:44] 一个755 [01:13:45] 一个644 [01:13:48] 755一个Hello [01:13:49] 一个Hello.c [01:13:52] Hello是可以执行的 [01:13:53] 然后这个指针 [01:13:54] 链接指向了 [01:13:55] 我可以把它拖过来一点 [01:13:57] 链接指向 [01:13:59] 这里指向它 [01:13:59] 这里指向它 [01:14:00] 这个视觉效果不完美 [01:14:01] 是因为我没有调 [01:14:02] 就是one shot直出 [01:14:05] 没有改 [01:14:05] 我可能稍微第二个prompt [01:14:07] 让它去掉了一点东西 [01:14:09] 就这些所有的问题都没有修 [01:14:15] 然后我们可以在这里 [01:14:17] 各种各样的探索 [01:14:20] Git的方方面面 [01:14:22] 比如说 [01:14:23] 比如说 [01:14:26] 我来把它拖过来一点 [01:14:32] 你现在可以创建分支 [01:14:35] 我可以让它创建 [01:14:44] 你可以讲清楚一点 [01:14:51] 我让它创建两个分支 [01:14:59] 然后你就直接看到动画效果了 [01:15:02] 就是左右是实时同步的 [01:15:05] 实时同步 [01:15:11] 没关系 [01:15:11] 你看这里有一个东西叫A [01:15:15] ref是heads下的A [01:15:17] 然后你的什么是一个分支 [01:15:20] 分支就是一个文件 [01:15:22] 叫refs [01:15:23] 它就是指针 [01:15:24] 所谓的分支就是指向这些东西的指针 [01:15:26] 然后HEAD 是指向指针的指针 [01:15:29] 它在磁盘上是这些字节 [01:15:31] 然后这个字节就是一个 [01:15:34] 指向了一个tree object [01:15:36] 这个东西就是一个tree object [01:15:38] 我们来看a [01:15:44] 你看a就是一个 [01:15:45] 就是一棵树 [01:15:46] 然后树里面有一个杠 [01:15:48] 然后杠里面包括了新的 [01:15:50] 这个新的hello.c [01:15:52] 你看它是int main(void)的 [01:15:53] 然后同样的b [01:15:56] 然后b也是这样的 [01:16:01] 它指向了这样的一个 [01:16:03] 它改过的带 [01:16:04] 二个c二个v的 [01:16:05] 视觉效果不是很好 [01:16:07] 但我相信我跟它对话几轮以后 [01:16:08] 应该就能搞定了 [01:16:10] 就得到了一个近乎完美的 [01:16:12] git的visualization [01:16:13] 好 我现在 [01:16:14] 你说 [01:16:15] 我说 [01:16:23] 我可以让它给我展示 [01:16:25] merge conflict的状态 [01:16:26] 这时候 [01:16:27] 你看就很正常的 [01:16:30] 很正常 [01:16:31] 你在两个开发者在开发的时候 [01:16:34] 你在假设两个开发者在开发的时候 [01:16:37] 一个人做了这个修改 [01:16:39] 就他们你想 [01:16:40] 你和你的团队成员 [01:16:41] 每个人都对这个代码不少 [01:16:43] 你们两个都冲动了 [01:16:45] 那就会进入冲突 [01:16:46] 就会进入这个状态 [01:16:47] A在这 [01:16:48] B在那 [01:16:49] 当你把那个A拉到本地的时候 [01:16:51] 你就会看到这个冲突状态 [01:16:52] 你就知道 [01:16:53] 你看冲突现场 [01:16:55] hello [01:16:55] hello的内容 [01:16:56] 是这样的 [01:16:57] 然后你可以看到 [01:16:58] 现在未提交的区域 [01:17:00] 在未提交的区域 [01:17:05] 你看在index里面有 [01:17:07] 有好几个 [01:17:08] 你看hello.c [01:17:12] 这是你看不到的 [01:17:14] 这是他还知道git [01:17:16] 悄悄的在这里面留了三个版本 [01:17:20] 叫base [01:17:20] 叫 ours [01:17:21] 然后这里 [01:17:23] 但他给你看到那个hello.c是这个 [01:17:27] 是这样的一个有尖括号的 [01:17:30] 然后你可以让他resolve [01:17:37] 你看那些临时文件没有了 [01:17:43] 这个merge [01:17:46] 创建了一个这样的 [01:17:49] commit object [01:17:50] 它的tree是这个 [01:17:51] 它有两个parent [01:17:54] 然后它有author [01:17:56] 然后你看到这个 [01:17:58] 指向的关系被正确的保留下来了 [01:18:02] 你看它的tree object选的是 [01:18:05] 选的是这个版本 [01:18:08] 我就不再展示了 [01:18:10] 所以看到 [01:18:12] vibe coding很好用 [01:18:15] 我觉得就你们这么大的时候 [01:18:17] 这么大年纪 [01:18:18] 尤其是你们有时间的时候 [01:18:20] 你们最大的乐趣 [01:18:21] 就是vibe code一个这个 [01:18:23] 然后vibe code完了以后 [01:18:24] 就各种调 [01:18:25] 然后跟着他学习 [01:18:26] 很快你的知识就爆炸式的增长 [01:18:31] 然后我再举一个例子 [01:18:32] 再举个例子 [01:18:33] 你们做CS [01:18:34] 你们上SICP的时候 [01:18:37] 的时候 [01:18:39] 有没有用过这个官方 [01:18:40] 这应该是官方的吧 [01:18:41] 这个code.cs61a.org [01:18:45] 有没有用过吗 [01:18:48] 这是一个Python IDE [01:18:49] 放大一点 [01:18:53] 这还是很好用的 [01:18:54] 你看 [01:18:54] 这是一个如假包换的Python IDE [01:18:56] 你可以运行它 [01:19:01] 可能它在加载一个Python的Runtime库到本地 [01:19:08] 但是可能它这个库在 [01:19:10] 我这里访问出来 [01:19:12] 并没有很快的地方 [01:19:13] 但这是一个IDE [01:19:15] 基本上完整功能的IDE [01:19:17] 然后它可以 [01:19:19] 你看它CS [01:19:20] OK [01:19:21] 它需要我劳力 [01:19:23] OK [01:19:23] 没问题 [01:19:24] 它可以完成作业 [01:19:32] 最近因为我们有一个小项目 [01:19:36] 所以我们也被迫 [01:19:38] vibe code了一个这个玩意儿 [01:19:41] 类似的东西 [01:19:44] 然后你已经看到这里面有slop [01:19:46] 这些slop都是 [01:19:47] 就是我直接让AI [01:19:48] 就是一句话去生成的 [01:19:52] 它可能也不是一个 [01:19:55] IDE级别的东西 [01:19:57] 但是 [01:19:59] 但是 [01:20:02] 我觉得效果还是不错的 [01:20:05] 在架构设计上 [01:20:06] 我稍微做了一点 [01:20:07] 就我稍微做了一点 [01:20:09] 自己的把控 [01:20:10] 我把了关 [01:20:11] 所以这个系统的架构还算比较好 [01:20:14] 它既可以作为一个网页的应用 [01:20:17] 去教大家编程 [01:20:18] 也可以作为一个 [01:20:20] 就是Electron的 [01:20:21] 像VS Code一样 [01:20:22] 一个EXE双击就能打开的 [01:20:24] 然后它可以可视化任何程序 [01:20:30] 比如说 [01:20:32] 因为我们要给小朋友 [01:20:34] 给三四年级的小朋友教Python [01:20:37] 这是一个很有挑战性的事情 [01:20:39] 因为第一节课如果你教他们PyCharm的话就全完蛋了 [01:20:44] 所以我要做一个极简的界面 [01:20:48] 整个都是外包的就是都是 AI slop 了 [01:20:51] 我也不再管控它的代码质量 [01:20:53] 但我对它的架构其实有把控 [01:20:56] 我会得到一个什么 [01:20:57] 我第一节课要给小朋友讲Python [01:20:59] 我就要给他一个印象Python很好玩 [01:21:02] Python很好玩 [01:21:03] 然后Python很好玩 [01:21:04] 我就说好 [01:21:05] 我可以把一个整数变成一个字符串 [01:21:09] 这个是你们熟悉的 [01:21:11] 你们没有想到 [01:21:13] 我把一个unicode字符的快快编码成一个整数 [01:21:17] 然后再把它decode回来 [01:21:18] 然后我还可以hello乘以10 [01:21:23] 我就可以打印10份 [01:21:25] OK [01:21:26] 这是上的第一节课 [01:21:29] 然后如果你经常讲汉诺塔 [01:21:33] 我有debugger [01:21:34] 这是我的调试器 [01:21:37] 你每次都可以 [01:21:38] 你看我在递归之前 [01:21:39] 我会告诉你 [01:21:42] 它应该怎么移动 [01:21:45] 然后我可以往后播放 [01:21:47] 它就会真的移过去 [01:21:50] 我可以换一个多一点的 [01:21:51] 然后我可以往前拖 [01:21:55] 也可以往后拖 [01:21:58] 就看起来 [01:21:59] 看起来就是一个不错的可视化 [01:22:03] 这也是外包的 [01:22:04] 这也是完全外包的 [01:22:06] 上课的负担就轻多了 [01:22:09] 负担就轻多了 [01:22:11] 但这还不是我们真正觉得好玩的 [01:22:16] 你想三四年级的小朋友 [01:22:18] 能编什么程序 [01:22:20] 三四年级小朋友能编什么程序 [01:22:22] 就这种玩 [01:22:25] 就我给他一个 API [01:22:27] 给他一个世界 [01:22:28] 然后他可以玩起来 [01:22:30] 我也可以挑事他 [01:22:31] 这是一个flat [01:22:32] 完全flat的程序 [01:22:39] 比如说 [01:22:41] 你可以看这个程序运行的过程 [01:22:46] 一个X、Y、Z 的循环 [01:22:52] 然后你会看到这个方块在从上往下掉 [01:22:56] 每一次都是真实的 [01:22:58] 我可以往前走往后退 [01:23:00] 然后这个视觉效果 [01:23:00] 你显然我就稍微调了一下 [01:23:02] 所以你看起来就不那么slop了 [01:23:03] 看起来包括你看这左边这个坐标轴是 [01:23:08] 坐标轴也是正确的 [01:23:10] 我还骂了一下AI [01:23:11] 第一版它实现的坐标轴 [01:23:14] 不符合我的预期 [01:23:16] 最后还可以play [01:23:18] 你看这动画是平滑的 [01:23:20] 我在左右拖的时候 [01:23:21] 它是平滑的 [01:23:24] 平滑动画 [01:23:25] 然后你想 [01:23:25] 我们的快乐来自于哪里 [01:23:28] 你们刚开始学编程的时候 [01:23:30] 就一个二重循环 [01:23:31] 人打一个星号 [01:23:32] 打一个三角形 [01:23:33] 你不就很开心吗 [01:23:35] 然后我现在一样 [01:23:36] 你学Python [01:23:37] 然后你什么都 [01:23:38] 你就可以3D打印了 [01:23:39] 这是一个3D打印的程序 [01:23:40] 你再叠块 [01:23:41] 你可以叠 [01:23:42] 可以挖 [01:23:42] 人就创造了一整个 [01:23:45] 整个世界 [01:23:46] 然后剩下所有东西 [01:23:48] 都是直接AI slop [01:23:51] 我管都没有管 [01:23:52] 程序也没有管 [01:23:53] 比如说Fibonacci number的递归 [01:23:55] Fibonacci number递归 [01:23:57] 然后insertion sort [01:24:03] 回到头上开始播放 [01:24:06] 插入排序是怎么样 [01:24:07] 一个一个比较的 [01:24:09] 然后你看到就太容易了 [01:24:12] 当然我是有一个skill [01:24:14] 可以把它编译 [01:24:16] 就是我有一个编译的skill [01:24:17] 可以把它编译成 [01:24:18] 把任何一个你想要的东西 [01:24:19] 可以编译成一个 [01:24:21] 在模型的能力下 [01:24:25] 我还试了DVC Flash [01:24:27] Vision的版本 [01:24:28] 它就是国产的模型 [01:24:30] 便宜的模型 [01:24:31] 就可以出效果 [01:24:32] 反正我觉得效果 [01:24:34] 已经达到一个 [01:24:35] 完全可用的状态 [01:24:37] 然后我就可以去挣钱了 [01:24:41] 在今天 [01:24:42] 你们真正有价值的品质 [01:24:44] 就是你们愿意去 [01:24:45] 花一点时间学习 [01:24:48] 然后你很快就会发现在求职的市场上面 [01:24:51] 把细节做好的这个bar不断的会提高 [01:24:54] 因为一旦只要有人学会了 [01:24:57] 招人的单位就会用这个标准来衡量你 [01:25:00] 那么你想我在面对一个研究生的时候 [01:25:02] 问他Gitmerge了以后会发生什么 [01:25:05] 他没有任何的概念 [01:25:07] 和一个在你们学每一个专业课 [01:25:11] 你学每一个算法 [01:25:12] 学每一个细节的时候 [01:25:13] 你都在AI的指导下帮你巩固了这个概念 [01:25:17] 真正消化的这个概念 [01:25:18] 你可以从零开始构建这些概念的时候 [01:25:22] 这两个人 [01:25:22] 他们在这个job market上面的竞争力 [01:25:25] 当然我觉得可能还有一件事情会发生 [01:25:27] 就是反正AI太强了 [01:25:29] 所有的人都没有竞争力了 [01:25:30] 那也许这是最好的 [01:25:33] 这可能是最好的状态 [01:25:35] 否则你想一想 [01:25:35] 你们的危机感 [01:25:37] 这个世界上有很多那样的人 [01:25:39] 然后你们如果不成为那样的人 [01:25:41] 你上了这个job market [01:25:43] 你就死了 [01:25:45] 所以我觉得上课耽误学习是真实存在的 [01:25:47] 因为你们的很多老师 [01:25:50] 真正好的东西 [01:25:51] 打开都没有打开过 [01:25:52] 那些真正好的代码 [01:25:53] 真正好的项目 [01:25:53] 符合工程规范的项目 [01:25:55] 他对着 [01:25:55] 对着那个课本 [01:25:57] 他就对着那个课本讲一遍 [01:25:59] 和今天你看到的 [01:26:00] 这个AI直出的东西 [01:26:03] 就刚才那个整个的框架 [01:26:05] 大概我就是两天的碎片时间 [01:26:08] 就全部完成了 [01:26:09] 包括视觉效果的调整 [01:26:10] 就完成了 [01:26:12] 就没有任何的成本 [01:26:13] 做这些事情 [01:26:14] 已经没有任何的成本了 [01:26:18] 所以我说我马上就淘汰了 [01:26:20] 我自己学习的时间是非常有限的 [01:26:22] 我很想学很多东西 [01:26:23] 但是我不像你们那么多 [01:26:25] 有那么多时间 [01:26:28] 大概是这样 [01:26:29] 我们讲了一个 [01:26:32] 然后其实 [01:26:33] 所以今天讲的 [01:26:35] 虽然我好像在讲 [01:26:36] 其实我讲的是 [01:26:37] Best Practice [01:26:38] 这里面的Best Practice还有好多 [01:26:40] 比如说刚才你 [01:26:41] 如果有注意力 [01:26:43] 会注意到 [01:26:44] AI给了我们一些 [01:26:52] 其实AI还是挺slop的 [01:26:54] 你看前面的commit message都是英文 [01:26:58] 都是一行 [01:27:00] 加上一个详细解释 [01:27:02] 然后你看这AI就开始自由发挥 [01:27:05] 它开始混了英文和中文了 [01:27:08] 但还好它还是遵循了这样的一个 [01:27:10] 有先有scope [01:27:12] 我干了什么 [01:27:15] 然后一个总结 [01:27:17] 再有message body这样的格式 [01:27:22] 你们看了很多就知道 [01:27:23] 这个东西存在 [01:27:25] 它实际上是有标准的 [01:27:27] 在今天你已经可以找到这样的 [01:27:29] conventional commits [01:27:32] 它告诉你这个文档很短 [01:27:34] 这个specification [01:27:39] commits must be prefixed by a wizard type [01:27:43] 是一个noun [01:27:43] 包括feature [01:27:45] 我如果要引入一个feature [01:27:46] 我要fix一个bug [01:27:49] 要一个冒号 [01:27:50] 一定要有一个空格 [01:27:52] 这些都是规范 [01:27:53] 然后有的时候就比如说 [01:27:56] 我和同学合作的时候 [01:27:58] 高血压时间还是比较多的 [01:28:00] 比如说如果文档 [01:28:02] 一般来讲 [01:28:02] 比如Markdown的文档 [01:28:04] 英文和中文之间要有一个空格 [01:28:07] 这是我不知道什么时候 [01:28:11] 大家开始用这个convention [01:28:13] 但是好像大家都会用这个 [01:28:17] 比如说你好 [01:28:17] 这是hello.c [01:28:19] 对我来说这是肌肉记忆 [01:28:21] 但如果我看到 [01:28:23] 我最难受的是 [01:28:25] 一个文档里面有一些是这样 [01:28:30] 你们注意力 [01:28:32] 你们注意到了吗 [01:28:35] 当我看到这个时候 [01:28:37] 我就是浑身难受 [01:28:38] 你哪怕这样 [01:28:39] 我都会认为 [01:28:40] 好像你是在遵循某一种convention [01:28:43] 你是保持一致的 [01:28:44] 但是如果你不一致的话 [01:28:48] 我真的是有点头大 [01:28:51] 这里有很多 [01:28:52] 所以AI会把这些细节给做好 [01:28:54] 而这些工程细节 [01:28:56] 其实都是为了工程师 [01:29:00] 在面对更大项目的时候 [01:29:02] 它有一种一致性和规范 [01:29:05] 它是一种 [01:29:07] 比如说你的代码 [01:29:09] 要以同样的方式排版 [01:29:11] 很自然的 [01:29:12] 你的文档也要以同样的方式排版 [01:29:14] 这样的一个 [01:29:17] 你人维护起来会更好 [01:29:19] 虽然我觉得在AI时代 [01:29:20] 可能也好像也不重要 [01:29:22] AI都会帮你 [01:29:24] 所以AI我还是很喜欢的 [01:29:27] 所有用AI的学生 [01:29:28] 都不会生成那样的slop [01:29:30] 至少对 [01:29:31] 至少不会生成那样的slop [01:29:34] 然后Git还有很多很多的best practice [01:29:37] 比如说你可以在网上找到各种 [01:29:42] Commit often [01:29:44] Perfect later [01:29:46] 这里有很多 [01:29:48] 这些其实都是有 [01:29:50] 工程实践的经验的 [01:29:53] 你们回去读一下这些文档 [01:29:56] 让AI帮你 [01:29:58] 你就会学到更多 [01:30:01] 比如说 [01:30:02] 为什么要及时commit [01:30:04] 是因为在复杂的项目里面 [01:30:07] 随着你修改的东西越多 [01:30:09] 你错的东西就越可能多 [01:30:11] 你在小项目刚开始的时候 [01:30:13] 其实不要紧 [01:30:13] 你一次提交个两三千行代码都OK [01:30:16] 但是一旦这个项目里面 [01:30:17] 有错综复杂的关系以后 [01:30:20] 你的一个大的Commit [01:30:21] 可能会break这个项目里面多处的东西 [01:30:24] 引入多个bug [01:30:25] 后面再要引入多个fix来fix它 [01:30:28] 那这时候你有一个很大的Commit [01:30:30] 比如VR你有一个一万行的Commit [01:30:32] 然后后面你修了一百个bug [01:30:33] 你都说这是VR产生的 [01:30:35] 这就很糟糕 [01:30:37] 甚至修不过来 [01:30:38] 你也不知道VR修好没有 [01:30:39] 而如果你每一次的Commit都是很小的 [01:30:42] 都只做一个修改 [01:30:43] 那冤有头债有主 [01:30:45] 你这个bug就是你写的 [01:30:46] 我修完了也就修完了 [01:30:47] 这两个committer是可以配对的 [01:30:49] 它给了我们审计 [01:30:51] 的能力 [01:30:52] 也给我们一些 [01:30:54] 比如说 [01:30:55] 你改了很多 [01:30:56] 然后不小心真的你的 [01:30:58] 自己的电脑就损坏了 [01:31:00] 物理损坏了 [01:31:00] 那你还没有来得及推到远端 [01:31:02] 你做一个小的提交 [01:31:04] 立即push [01:31:05] 那么你就可以得到一个互联网上的备份 [01:31:10] 你的电脑马上坏了 [01:31:12] 也好 [01:31:15] 这个AI是很清楚 [01:31:16] 他知道你可以 revert [01:31:18] 可以revert [01:31:18] 可以bisect [01:31:19] 二分查找刚才 [01:31:20] 刚才找到的 [01:31:21] 然后还有一些 [01:31:22] 我后面可能会讲 [01:31:24] 比如说Hooks和CI [01:31:25] 然后这样的 [01:31:31] 以及你们 [01:31:34] 如果还有惊人的注意力 [01:31:35] 应该观察过 [01:31:37] 就你们的项目 [01:31:38] 可能托管在GitHub [01:31:39] 或者GitLab上 [01:31:40] 那么你会看到 [01:31:44] 你的AI agent会试图调用一些gh 或者 glab的命令 [01:31:49] 然后你会看到一个什么command not found [01:31:51] 然后它就回退到古法编程 [01:31:54] 又开始写Git的命令 [01:31:56] 是因为GitHub有完整的 [01:31:58] GitHub实在是太popular了 [01:32:01] 它太好用了 [01:32:02] 那么我就值得做一个命令行工具 [01:32:07] 把non-Git的部分 [01:32:09] Git管理了好多代码的快照 [01:32:11] 但是GitHub上还有issue [01:32:14] 我今天可以发一个文本 [01:32:15] 我还可以对你的代码做comments [01:32:18] 然后这些东西都是不随着这个Git的tree [01:32:21] 做persistence的 [01:32:23] 那么如果我们想要管理它 [01:32:24] 做一个命令行工具 [01:32:25] 我可以去fetch一个issue [01:32:27] 然后可以找assign给我的issue [01:32:29] 你要登录 [01:32:30] 然后找到assign给我issue以后 [01:32:31] 我还可以把这个issueclose了 [01:32:34] 那这个命令行工具的agent就很好用 [01:32:36] 它可以 [01:32:38] 你装了GH以后 [01:32:40] 我就可以在agent里面设置一个goal [01:32:42] 说给我完成一个什么很长 [01:32:44] 可以设置一个很长的任务 [01:32:46] 我会要求他做小步的提交 [01:32:52] 做可以审计的提交 [01:32:53] 然后怎么分支的规则 [01:32:55] 线性的历史或者等 [01:32:57] 然后你可以看到我的 [01:32:58] 比如说我的这个项目 [01:33:06] 就有大量的你看 [01:33:10] 有些对 [01:33:11] 从我开始登代码开始 [01:33:13] 大量的这样的提交 [01:33:16] 然后我很多时候都不管他们 [01:33:26] 讲了很多Git [01:33:30] 其实这门课讲的还是软件工程 [01:33:33] 所以最后这小段总结 [01:33:35] 是今天更重要的 [01:33:38] Git本质上是一个软件 [01:33:40] 它说我们要把项目组织成 [01:33:43] 用Atomic Change推进的快照 [01:33:45] 这是你们今天要记住的事情 [01:33:47] 然后所有的一切 [01:33:48] 都是从这句话里面推出来了 [01:33:51] 你们组织一个软件的项目 [01:33:54] 你要管理很多的快照 [01:33:55] 这样就可以随时 [01:33:58] 开启一个新的分支 [01:34:00] 做一些尝试 [01:34:01] 如果失败了 [01:34:02] 你就可以把它安全的扔掉 [01:34:03] 如果成功了 [01:34:04] 你就可以把它合并进来 [01:34:05] 然后你甚至可以 [01:34:06] 同时推进多个主线 [01:34:08] 然后以合并的方式 [01:34:09] 让它完成 [01:34:10] 而不会互相干扰 [01:34:11] 因为如果你们 [01:34:13] 做大作业 [01:34:14] 自己古法做大作业肯定有这个感受 [01:34:16] 你如果要改好几个地方的时候 [01:34:18] 改着改着很可能就改乱了 [01:34:20] 你跟这个部分和那个部分冲突 [01:34:23] 这个部分和那个部分冲突 [01:34:27] 所以项目管理的本质 [01:34:30] 是人类没有办法处理一团混沌 [01:34:33] 很多需求 [01:34:34] 很多代码 [01:34:36] 一次做一大波 [01:34:38] 所以很自然的 [01:34:39] 我们要把需求分解成可以理解 [01:34:42] 可以审计的小任务 [01:34:43] 一小步一小步的 [01:34:44] 然后这样出了问题 [01:34:45] 就可以bisect [01:34:46] 可以blame [01:34:47] 协作的时候可以blame [01:34:48] 发生冲突也容易解决 [01:34:50] 两个小任务发生冲突 [01:34:52] 甚至比如说 [01:34:53] 我有可能是在 [01:34:55] 在这样的一个公共的祖先上面 [01:34:57] 我可以做了好多事 [01:34:59] 我可以做好多事 [01:35:01] 如果每一个都很小 [01:35:03] 那么如果发生了冲突 [01:35:06] 可能我能知道 [01:35:06] 哪一个小的提交是冲突的 [01:35:09] 那么剩下的不冲突的部分 [01:35:12] 他们合并起来可能会比较容易 [01:35:17] 然后包括有些项目会要求linear history [01:35:19] 我们做可能更严肃的项目 [01:35:21] 也会要求所有的main [01:35:24] 我们的合并 [01:35:26] 合并可以是 [01:35:28] 比如说我这样了 [01:35:29] 然后我把这个提交合上来 [01:35:31] 这样就像刚才我们看到一样 [01:35:33] 我的一个commit [01:35:35] 可以有两个parent [01:35:37] 有两个组成 [01:35:39] 然后当然它可能又继续 [01:35:41] 可能继续又分开 [01:35:42] 又分叉又合并 [01:35:43] 然后最后会变成一个 [01:35:46] 很典型的Git的结构 [01:35:48] 但是现在 [01:35:49] 好像大家更流行一个Linear History [01:35:52] 因为反正都是AI [01:35:54] 反正它Resolve [01:35:55] 它Rebase都很容易 [01:35:57] 我就强行要求 [01:35:58] 我的整个commit history [01:35:59] 都是线性的 [01:36:00] 好像是线性的一样 [01:36:02] 当然这个也不完全是 [01:36:05] 所有的都是好处 [01:36:06] 但至少对于 [01:36:09] 现在我们维护项目来说 [01:36:10] 它会让历史更易于追溯 [01:36:12] 而且他对cherry-pick和blame也更友好 [01:36:17] 在无限大能力的模型到来之前 [01:36:19] 那么这件事情都是好的 [01:36:22] 你把Git记录拆分的非常好 [01:36:24] 你很容易的找到这次修改是由谁引入的 [01:36:27] 那么AI维护项目的效率也会高一点 [01:36:31] 也会省token一些 [01:36:32] 有些文件他就可以不用去读了 [01:36:36] 然后我还有一些经验可以跟大家分享 [01:36:41] 比如说我们怎么 [01:36:43] 就虽然我们知道是要小步向前进 [01:36:45] 我们怎么管理一个项目 [01:36:47] 我会在架构设计的阶段 [01:36:49] 先用单线程 [01:36:50] 用一个智力比较高的模型跟他讨论 [01:36:53] 这个其实很重要 [01:36:56] 因为如果你的软件设计的不好 [01:36:58] 你后面就是无穷的迭代的 [01:37:01] 我在第一次课的时候 [01:37:02] 其实就给大家展示了这个例子 [01:37:04] 我会把组件库的接口定的非常好 [01:37:07] 我可以给大家看一下 [01:37:11] 找一个例子 [01:37:22] 就是我为什么这么信任 [01:37:24] 为什么这么信任AI的代码 [01:37:28] 看起来 [01:37:31] 我这里就这个项目里面 [01:37:33] 有很多互相关联的组件 [01:37:38] 我们的程序会产生 [01:37:41] 会运行产生结果 [01:37:42] 然后我拖动这个进度条 [01:37:45] 它不仅要关联到 [01:37:47] 我的程序的编辑器 [01:37:48] 还要关联到这个上面 [01:37:52] 然后但我把绝大部分的接口 [01:37:55] 都设置成了functional的 [01:37:56] 就是pure function [01:37:57] 包括我的trace [01:38:00] 是程序的一个pure function [01:38:02] 然后大概我有一个标准的 [01:38:03] 这个程序的derivation的graph [01:38:06] 我说你要按照这种方式来展开 [01:38:08] 然后所有的这个 [01:38:12] 就全局互相影响的状态 [01:38:14] 我都要注册到一个全局的地方 [01:38:16] 然后这个全局的状态 [01:38:17] 比如说我光标的位置 [01:38:18] 光标的位置可能会怎么样影响这个可视化 [01:38:21] 全部都包括这个菜单条 [01:38:23] 这个非常干净非常小的状态 [01:38:25] 然后所以你看到的一个这样的一个playground [01:38:27] 基本上核心的共享状态 [01:38:30] 也就只有十几行 [01:38:33] 十几行的共享状态 [01:38:35] 然后一个十几行的组装 [01:38:36] 然后剩下所有东西都是黑盒子 [01:38:39] 我都要求他 [01:38:40] 我就直接会要求agent在设计这些visualization的时候 [01:38:43] 禁止看任何库函数的实现 [01:38:45] 只看接口 [01:38:46] 所以它的非常其实非常省 token 的 [01:38:50] 就几万个token就可以做一个 [01:38:52] 做一个这样的 [01:38:54] 做这样的playground [01:38:55] 然后一旦这样的架构设计完成了以后 [01:38:59] 我就会开三个 [01:39:01] 这个或者开几个terminal [01:39:04] 然后我要求它 [01:39:05] 我的AGENTS.md有一个明确的协作规则 [01:39:08] 我看在不在了 [01:39:12] 可能改过一些 [01:39:13] 对agent [01:39:14] 对我有一些协作规则 [01:39:18] 比如说我让他不需要 [01:39:19] 调用视觉模型去建设 [01:39:22] agent很喜欢做这件事 [01:39:23] 然后花很多的 [01:39:23] 很多token [01:39:24] 然后我也不调用浏览器任务 [01:39:26] 我在边上放一个 [01:39:27] 另外一块屏上放一个浏览器 [01:39:29] 然后他是及时刷新的 [01:39:31] 然后我就 [01:39:32] 我就开始开三个agent [01:39:34] 哪里不满意就骂他 [01:39:36] 然后你就可以肉眼看着 [01:39:38] 右边那个东西 [01:39:38] 不停地变好变好变好变好 [01:39:40] 最后问题就 [01:39:42] 问题就解决了 [01:39:44] 这是一个agent能力之内的项目 [01:39:46] 然后我非常确信的是 [01:39:48] Agent能力会不断地增长 [01:39:49] 然后越来越多的项目 [01:39:52] 能够被Agent [01:39:53] 以这样的方式 [01:39:54] 就是一个比较轻量的讨论 [01:39:55] 然后剩下完成 [01:39:59] 这是一个非常可怕的事情 [01:40:00] 就是如果Agent快了1000倍 [01:40:05] 我们还怎么样管理 [01:40:07] 这个版本控制 [01:40:09] 我们可以下一节课再来讨论 [01:40:11] 好 今天下课