[00:00:00] 上一节课的时候 [00:00:01] 我们讲了软件工程的 [00:00:03] 来龙去脉 [00:00:04] 说在什么时候碰到了软件危机 [00:00:07] 然后大家发现那个时候软件开发不下去了 [00:00:09] 我们需要一个工程化的方法来 [00:00:11] 解决软件里面的问题 [00:00:13] 然后也看了那个Royce的那个老paper [00:00:16] 他说 [00:00:17] 我们希望的大概虽然是大概 [00:00:21] 是希望有一个intent [00:00:22] 然后能够走向spec [00:00:24] 然后最后走向implementation [00:00:27] 然后最终可以产出一个软件 [00:00:29] 当然有了implementation以后 [00:00:30] 你还需要维护它 [00:00:31] 软件的生命周期没有结束 [00:00:33] 所以很自然的 [00:00:35] 这个一路向下就有一个瀑布一样的过程 [00:00:41] 我在intent的在设计的或者需求的阶段 [00:00:46] 可能会得到我对这个软件的一些认识 [00:00:48] 然后这些认识逐渐的会啊进入这个specification的space [00:00:53] 它会形成这个依赖的关系 [00:00:55] 然后在系统实现的时候 [00:00:57] 你又会有各种模块 [00:00:59] 函数 [00:01:00] 代码 [00:01:01] 测试 [00:01:01] 各种各样的这个软件里面的artifacts [00:01:04] 然后这又会跟 [00:01:05] 其实当然这是直接和间接的这种data dependency [00:01:09] 然后就形成了一个错综复杂的交错的网络 [00:01:12] 然后这就是软件工程的一个起点 [00:01:15] 所以Royce 批判了这样的一个模式 [00:01:17] 他说OK如果你要按照这样开发的话 [00:01:19] 很可能你在后面 [00:01:22] 他的分析其实就来自于这个data data [00:01:24] Data dependency [00:01:25] 如果我在后面这个时候发现有一个东西错了 [00:01:29] 然后这个错一直会反推 [00:01:31] 回溯到前面的某一个东西错了 [00:01:33] 而前面的某一个东西 [00:01:34] 如果在事后被证明是错的 [00:01:36] 一个设计不合理 [00:01:37] 实现达不到预期 [00:01:38] 然后你的需求或者设计做错了 [00:01:40] 那它会导致后面一个很大的部分 [00:01:44] 它会污染到的部分 [00:01:46] 测试到的部分都会失效 [00:01:48] 然后你可能会需要重做 [00:01:49] 会带来大量的成本 [00:01:51] 然后在这样的一个背景下 [00:01:54] 软件工程就诞生了 [00:01:55] 所以软件工程研究的是在这个各个的 [00:01:58] 做需求分析 [00:01:59] 做系统设计 [00:02:00] 做系统实现的时候 [00:02:01] 我们有什么样的方法 [00:02:03] 不管是有人文关怀的方法 [00:02:05] 你需要用checklist [00:02:06] 然后说这个产品经理应该怎么管人 [00:02:08] 这其实也是软件工程的一部分 [00:02:12] 到比如说你写代码的时候应该怎么写测试 [00:02:14] 我们有没有可能做自动化的测试 [00:02:16] 自动化的分析 [00:02:17] 然后展开了一整个这个软件工程里面的故事 [00:02:21] 然后其中一个比较有趣的 [00:02:23] 一个在pre-AI时代的尝试 [00:02:25] 因为人很难对齐 [00:02:27] 人和人很难对齐 [00:02:29] 而且我如果不写文档的话 [00:02:31] 很有可能如果只剩一份代码 [00:02:34] 你甚至比如说出错了 [00:02:35] 都不知道应该去追溯到过去的什么时候 [00:02:38] 所以在那个时候 [00:02:39] 软件工程里面提出了很多重要的概念 [00:02:41] 比如说traceability [00:02:43] 可追踪性 [00:02:43] 我们的每一个是最理想情况下 [00:02:47] 每一个测试用例或者每一个代码的片段 [00:02:51] 它都能够关联到 [00:02:54] 可以trace到一个过去发生的事情 [00:02:58] 做过的设计上 [00:02:59] 这样整个系统里面到底发生了什么 [00:03:01] 我改它会影响谁 [00:03:02] 我改设计会影响哪些测试用例 [00:03:05] 都能维护起来 [00:03:06] 然后如果我们有一个语言 [00:03:07] 一个结构化的 [00:03:08] 有语义的语言来表示它 [00:03:10] 那显然是更好的 [00:03:11] 所以就有了 [00:03:12] UML [00:03:13] 他试图去对齐整个世界上面的软件工程的实践 [00:03:16] 这当然都是都是有智慧的 [00:03:19] 然后我们回过头来再想一下这个过程 [00:03:24] 实际上软件工程它困难的 [00:03:27] 还有一个本质的原因 [00:03:28] 其实要软件它不软 [00:03:30] 就我们觉得什么东西是软的 [00:03:33] 你们现在坐在教室里面的这个椅背 [00:03:36] 它就是软的 [00:03:37] 它是软的 [00:03:38] 然后你当我们说软的时候 [00:03:40] 它有一个就是什么东西是软的 [00:03:42] 它有弹性 [00:03:43] 它你当你对它施加一个这个 [00:03:46] 比如说你把它摁摁下去 [00:03:48] 你一靠 [00:03:49] 它就瘪下去了 [00:03:51] 但是呢你做起来以后 [00:03:53] 他又诶弹回来了 [00:03:55] 你下次做的时候它还是好的 [00:03:56] 但软件和这样的就是物理世界里面的这些 [00:03:59] 比如说你的椅背很不一样 [00:04:01] 它是一个纯粹的逻辑制品 [00:04:03] 这个逻辑制品是容不下任何细微的差错的 [00:04:07] 作为一个程序 [00:04:08] 它有形式语义 [00:04:09] 你可以想象你们在写代码的时候 [00:04:11] 从来没有考虑过 [00:04:12] 如果你的程序里面的内存 [00:04:15] 可能会发生一个bit的翻转 [00:04:17] 你的程序还对吗 [00:04:19] 肯定不对了 [00:04:20] 你想呢你的你有一写了一个什么程序 [00:04:24] 从stdin输入一个N [00:04:27] 然后现在哦是很好 [00:04:28] 输入N等于一 [00:04:29] 然后N等于一 [00:04:30] 我就接下来循环就会从额数数字数组里面 [00:04:34] 读一个元素到 buffer 里,好 [00:04:35] 这个时候 [00:04:37] 如果你的软件是运行在卫星上的 [00:04:39] 然后有一个宇宙射线击中你的内存 [00:04:41] 然后那个一 [00:04:42] 他的unsigned integer的最高位变成了一好 [00:04:46] 他突然就变成了一个巨大无比的书 [00:04:48] 然后你整个程序就彻底的collapse了 [00:04:50] 它会无限制的去从输入里面读数 [00:04:54] 然后如果没有的话 [00:04:54] 那可能会等 [00:04:55] 然后你的程序表现出来 [00:04:56] 就是他喊了也可能在读了 [00:04:58] 比如说一百一千个数字以后产生一个 [00:05:01] 这个memory error [00:05:03] 所以软件其实一点都不软 [00:05:04] 刚才我说的是一个比较极端的例子 [00:05:06] 就是它运行的环境可能是你不可预见 [00:05:10] 但其实大家是在data center里面会观察到 [00:05:13] 比如说你的处理器随着制程越来越先进 [00:05:16] 它在这个热噪声下 [00:05:17] 有些指令就他那个密度特别大的那个指令 [00:05:20] 它可能会产生这种silent error [00:05:23] 就这条指令 [00:05:24] 你在这台机器上执行和在那台机器上执行 [00:05:27] 它结果就不一样 [00:05:28] 而且甚至是可能要这个服务器运行一段时间以 [00:05:31] 后才会出现这样的缺陷 [00:05:33] 就是制造时候的缺陷 [00:05:35] 所以软件本身就是一个 [00:05:38] 大家认为是集成了人类智慧的啊一个东西 [00:05:43] 然后同时它又要承载 [00:05:45] 对吧错客户来的这些这些需求 [00:05:48] 就比如说啊教务系统 [00:05:50] 我今天会花时间来讲教务系统哼 [00:05:53] 应该怎么样设计 [00:05:54] 应该怎么样 [00:05:56] 防止它堆积成屎山好 [00:05:59] 那就是软件工程 [00:06:02] 然后在这个背景下 [00:06:04] 这个我发现 [00:06:07] 就是也许你们学习 [00:06:09] 比如说可能有听课的是软件工程的同学 [00:06:12] 然后学计算机专业的同学肯定要上软件工程 [00:06:15] 工程的课程 [00:06:17] 软件工程是一个关乎整个软件生命周期的学科 [00:06:21] 但是呢你们被迫只能从底下这个地方开始学 [00:06:26] 因为如果你不理解代码是什么 [00:06:29] 如果你写不出代码 [00:06:30] 没有体会过码农开发软件的痛苦 [00:06:33] 那你是不可能做好一个产品经理的 [00:06:36] 你设想的 [00:06:37] 这个事情应该可以办到 [00:06:39] 这个事情花多少成本可能可以办到 [00:06:41] 那如果你没有一手的经验 [00:06:43] 你确实是做不到 [00:06:44] 所以你们上来以后就先学C语言 [00:06:46] 先学一个最基础的 [00:06:47] 老的已经过时了 [00:06:48] 没有人用的 [00:06:49] 也还算有一些人用的编程语言 [00:06:51] 然后在这个基础上你不断的产生新的知识 [00:06:54] 知道世界是怎么失败的 [00:06:55] 然后新的语言是怎么创造出来的 [00:06:57] 为什么要有它 [00:06:58] 然后一点一点去往上往上走 [00:07:01] 1.1点去往上走 [00:07:03] 然后然后你今天看到的是像需求 [00:07:07] 产品架构 [00:07:09] 这是我今天要讲的主题 [00:07:11] 在在这个软件的设计的阶段 [00:07:13] 到spec [00:07:14] 从intent到spec的阶段 [00:07:16] 然后这其实是软件 [00:07:18] 决定了一个软件系统从零开始走向的 [00:07:22] 最关键的部分 [00:07:23] 我们前面上课的时候先讲了版本管理 [00:07:26] 为什么先讲版本管理 [00:07:27] 因为你总是要从一个空的软件项目出发 [00:07:30] 你说大B1的时候总是从一个空的出发 [00:07:33] 那我们先讲一下如何从空的出发 [00:07:35] 这个软件怎么样往后走 [00:07:36] 然后接下来下一步 [00:07:37] 我们就要把控软件执行的方向 [00:07:40] 软件的走向 [00:07:41] 你的船到底是往东南西北哪个方向开 [00:07:46] 那你一开始的方向选了以后 [00:07:49] 对后面的影响是非常大的 [00:07:51] 但是呢像现建的需求 [00:07:53] 产品架构都很难做出量化的研究 [00:07:55] 他只能要么就是安利说今年没办法 [00:07:57] 我只能这样安利 [00:07:58] 要么就是这个你就当澳门科学基概念 [00:08:03] 他会给他刷一个 [00:08:04] 需求要做 ABC,架构要(满足什么) [00:08:07] 要做ABBC产品要满足什么什么什么好 [00:08:10] 然后当最让你看到的是 [00:08:11] 我们的学校里面有很多做软件工程的人 [00:08:15] 还我们号称南京大学软件工程世界第一 [00:08:18] 在CSRankings里面 [00:08:19] 如果你属那个ICSE的那个文文文章的话 [00:08:23] 然后 [00:08:23] 其实他们大部分都集中在这个和编码相关 [00:08:26] implementation相关的啊东西 [00:08:29] 可能有些比如说做这种NLP的 [00:08:33] 他说他会说好 [00:08:35] traceability在软件世界里面是非常重要的 [00:08:38] 这个correct好 [00:08:39] 那我们能不能训练一个神经网络模型来 [00:08:42] 这个直接从数据当中推导出这个 [00:08:45] 但它就变成 [00:08:47] OK那可能也许用GPT-6就可以做到 [00:08:50] 做到对 [00:08:52] 但我今天还是要讲一讲 [00:08:54] 所以我今天就要讲一讲这个里面最重要 [00:08:57] 或者说我觉得对于尤其在AI时代 [00:08:59] AI时代 [00:09:00] 你生产一个目标明确的代码 [00:09:03] 已经变得非常便宜了 [00:09:04] 而且现在比如说对我来说 [00:09:06] 我现在让AIAI生成生成代码 [00:09:09] 我只要把它的边界划定清楚 [00:09:11] 我其实真的已经不看他的视线了 [00:09:14] 一会我会给大家看到那个GPT-6 [00:09:16] 昨天写的那个那个代码 [00:09:17] 那个那个时间已经超越人类能够理解的范畴了 [00:09:20] 但是它是对的 [00:09:21] 我还能我还能比较好的信任他 [00:09:24] 如果我把它边界划定的清楚 [00:09:25] 然后并且我能够知道他怎么样做一个好的 [00:09:28] validation的话 [00:09:29] 那么我可以把agent的slop 铺开去 [00:09:32] 帮我实现这个 [00:09:34] 还是有一定质量保证的 [00:09:37] 软件的 [00:09:38] 那剩下的其实我觉得我们做软件系统的压力 [00:09:42] 或者说能够决定这个软件系统最后能走多远 [00:09:47] 比如说我们说一总是做一个minimum viable product [00:09:51] 这个这个没有问题 [00:09:53] 因为做minimum viable product可以让你快速的 [00:09:58] 找到一个从intent到specification [00:10:01] 到implementation的一个切片 [00:10:03] 然后在这个切片上 [00:10:04] 你可以走完整个软件完整的流程 [00:10:06] 但它不完备 [00:10:07] 那你在做这个MVP的时候 [00:10:09] 你一定想的是先做最重要的功能 [00:10:13] 你并不会想着你在做MVP的时候 [00:10:15] 就去想那个按钮那个样式应该长什么样 [00:10:18] 因为这也是depend的 [00:10:21] 你的按钮是你的按钮的样式 [00:10:23] 你判的你整个界面的设计 [00:10:25] 然后整个界面的设计 [00:10:26] 就是depend到整个你的所有功能的实现 [00:10:30] 那如果他不确定的话 [00:10:31] 那你叫我炸 [00:10:34] 如果你现在就去调那个按钮的样式 [00:10:36] 当上面的设计发生改变以后 [00:10:38] 你觉得这个按钮的样式很有可能就不可视了 [00:10:40] 所以所以你会你会做一个 [00:10:42] 你会有一个倾向 [00:10:43] 你不要做无用功 [00:10:44] 不要去研究一个你即将会扔掉的东西 [00:10:48] 每每个人都有这个本能 [00:10:49] 那如果你确实有这个本能 [00:10:51] 那么你就应该知道在做MVP的时候 [00:10:54] 选哪些最重要的功能 [00:10:56] 你会得到一个可以运行的版本 [00:10:58] 好没问题 [00:11:00] AI会给你一个 [00:11:02] 但是AI就在今天 [00:11:04] 至少今天的AI [00:11:06] 他没有去从需求产品设计架构的角度 [00:11:10] 除非你去显示的用prompt去告诉它 [00:11:13] 甚至哪怕可能是几个字告诉它 [00:11:15] 你都会能够 [00:11:17] 在你从一个零开始项目的零开始 [00:11:21] 然后如果你要让他用 [00:11:24] 比如说这个client server的架构 [00:11:27] 或者你就是一个你让他做一个client-server 的架构 [00:11:30] 你同样的这个一个MVP的需求 [00:11:34] 需求定下来 [00:11:35] 它加上几个字的prompt [00:11:37] 你都会得到完全不同的MVP [00:11:41] 他们可能功能上都差不多 [00:11:42] 看起来就都是实现了你的一个好 [00:11:45] 比如说我要做一个coding agent [00:11:46] 或者做一个你的Lab 1的那个学生的agent [00:11:49] 但是你的 [00:11:50] 这几个字的prompter都会都会形成一个带有 [00:11:55] 这个都会膨胀就 [00:11:57] 比如说如果你有client-server [00:11:58] 那你这里面可能就有个cli.py [00:12:01] 或者是depend一个这个JavaScript里面的这种 [00:12:05] 像Claude Code用的这种命令行的库 [00:12:08] 那如果你先要求这个client-server [00:12:11] 那他可能就会先设计这个数据的存储 [00:12:13] 然后他会给你选一个数据库好 [00:12:15] 它就它只要你一旦这个东西 [00:12:17] 比如说db.ts s [00:12:20] 你他一旦这个文件写了 [00:12:22] 它就会进入agent的上下文 [00:12:24] 然后每当你继续想要做 [00:12:26] 继续向后做这个需求 [00:12:28] 下一个需求的时候 [00:12:30] 都会带上之前的所有的历史 [00:12:33] 而这在软件工程里面也有一个名术语 [00:12:37] 叫做技术债 [00:12:38] technical debt [00:12:39] 他是债务 [00:12:40] 就说你在你在做任何决定的时候 [00:12:43] 你就背上了这个债务 [00:12:45] 因为这个决定你在做MVP的时候 [00:12:47] 你不知道这个 [00:12:50] 那你的决定性来自于需求 [00:12:53] 比如说这个我对我来说 [00:12:54] 我到底要一个什么功能代理 [00:12:57] 我显然是写了需求的 [00:12:59] 但这个需你要有一个自己的自己版本的需求 [00:13:04] 你会你会 [00:13:06] 就比如说你就想做一个这不帮助你学习的 [00:13:09] 或者说你已经进入了课题组 [00:13:11] 你对读研究论文这件事情 [00:13:14] 就有很强的需求 [00:13:15] 那你看到在不同的这个需求下 [00:13:18] 你的软件的走向肯定是不同的 [00:13:20] 然后再加上你的产品的设计 [00:13:22] 那我直面一下你最终的产品 [00:13:24] 这家比你想象的这个产品是只有你自己用的 [00:13:27] 比如我自己有很多自己用的用的非常顺手 [00:13:30] 但是我想给另外一个人用 [00:13:31] 他就用不那么舒服了 [00:13:33] 因为我有些我自己的知识 [00:13:36] 还有自己的偏好 [00:13:37] 对票对别人来说可能是受不了不习惯 [00:13:39] 比如说喜欢的命令 [00:13:40] 内行的工作的普通人肯定不要 [00:13:43] 除此之外还有个隐藏的东西,是架构 [00:13:46] 就是这个软件会变成什么样 [00:13:48] 而这三段会成为撬动软件生产力真正的杠杆 [00:13:52] 也就是说你可能是极端的档次 [00:13:54] 但是会对你的AI生成的stop的走向 [00:13:58] 技术带的累积带来一个非常大的影响 [00:14:02] 所以我会花两节课的时间来讲这个需求和 [00:14:06] 需求和架构,对 [00:14:13] 那我可以让他诊诊断一下 [00:14:16] 曾经对 [00:14:17] 曾经这个是 [00:14:19] 我刚才翻车了 [00:14:20] 我的系统UI还在正常工作 [00:14:24] 然后我的OBS还在正常的推流 [00:14:26] 但是我不知道不知道发生什么 [00:14:28] 因为我的所有的设备都不能用了 [00:14:30] 我鼠标键盘都没用了 [00:14:31] 我插上一个新的鼠标也没有 [00:14:33] 也没有用 [00:14:34] 所以以前啊当然这也是一个软件工程实践 [00:14:38] 出了问题你要去看日志 [00:14:40] 这imply你在写软件的时候 [00:14:42] 需要输出足够多的日志 [00:14:44] 这个好像AI就如果你不告诉AI的话 [00:14:46] AI也不会主动做 [00:14:48] 它会生成一个没有任何日志的软件系统 [00:14:51] 它都会留下这样的这个技术债 [00:14:54] 然后我不确定 [00:14:56] 我不确定能不能找到这个问题 [00:14:57] 但是啊我们回到今天 [00:15:01] 我们要讲的内容是需求工程 [00:15:04] 我们说曾经需求工程很难讲 [00:15:06] 因为学生不会去见这个真正的客户 [00:15:09] 也这个做不了 [00:15:11] 需求级的软件系统 [00:15:13] 你们从刚才说从最刚开始的时候学编程语言 [00:15:17] 到你毕业的时候 [00:15:18] 你的能力的增长 [00:15:20] 不足以让你去面对一个真正的软件 [00:15:23] 软件的客户 [00:15:25] 当然现在这个事情有点变了 [00:15:27] 你看在AI之前 [00:15:29] 我们有很少量的精英独立开发者 [00:15:32] 你们经历了几年的开源社区的洗礼以后 [00:15:34] 终于觉得自己可以独立了 [00:15:35] 做开发者的 [00:15:37] 然后会有一些付费用户 [00:15:38] 然后现在变了 [00:15:39] 现在这个独立开发者就爆炸了 [00:15:41] 然后付费用户瑟瑟啊瑟瑟发抖 [00:15:44] 然后我们今天要讲的软件系统的例子 [00:15:48] 是我比较喜欢的这个教务系统 [00:15:50] 教务系统 [00:15:51] 我可以称它是史诗级的需求爆炸 [00:15:55] 我昨天就趁趁机看了一下 [00:15:58] 然后我发现 [00:16:02] 我登录还在吗 [00:16:03] 好我的登录不在了 [00:16:05] 让我扫码登录一下 [00:16:10] 我需要扫码登录 [00:16:14] 扫码登录 [00:16:16] 好的 [00:16:19] 好我发现教务系统这几年一直都在啊进步 [00:16:25] 虽然这是这是我的界面 [00:16:26] 我稍微放大一点点 [00:16:28] 好好 [00:16:31] 我最近发现史诗级的更新 [00:16:34] 我可以问 [00:16:35] 仙林教学楼的灯一直没有解决 [00:16:43] 我应该打电话给谁 [00:16:48] 谁 [00:16:51] 我可以直接搜索 [00:16:55] 我正在理解您的问题 [00:16:57] 我不知道诶 [00:16:59] 他这个 [00:17:02] 就基建处 [00:17:03] 基基建基建处 [00:17:05] 但是我在那个系统上面 [00:17:06] 平台上面提了 [00:17:07] 然后基建处就把我打回来了 [00:17:09] 说这个事情不归不归我们管这个回 [00:17:13] 回头再给他打电话好 [00:17:15] 那我现在在上什么课 [00:17:18] 我不知道这个行不行 [00:17:23] 我现在在上什么课 [00:17:26] 我不确定他有没有载入我的我的上下文 [00:17:29] 我猜是可以的 [00:17:29] 因为我登录了 [00:17:34] 诶他还是教师课表本科诶 [00:17:43] 这是我的我的课稿 [00:17:45] 还是 [00:17:46] 这是一个史诗级的更新 [00:17:48] 你们竟然 [00:17:50] 不知道 [00:17:53] 但我相信大家应该不知道 [00:17:54] 突然有这个功能了 [00:17:56] 我不知道什么时候加上的 [00:17:57] 曾经肯定是肯定是没有的 [00:18:00] 因为因为好像我不会去搜索 [00:18:02] 我 [00:18:02] 因为我总是在那个快捷搜索栏里 [00:18:04] 直接等到它下一个界面弹出的时候 [00:18:07] 我就我就点它 [00:18:08] 所以这是一个非常典型的需求爆炸的系统 [00:18:12] 这个这个系统肯定是花了很多钱的 [00:18:15] 然后这个钱我觉得某种程度上 [00:18:17] 还不如变成token的补贴给大家 [00:18:20] 因为他要购买算力 [00:18:21] 他要购买开发 [00:18:22] 然后他 [00:18:23] 不如我 [00:18:24] 我刚才如果我用PyAgent去找一下的话 [00:18:27] 可能就一毛钱两毛钱 [00:18:29] 所以把token然后送给大家 [00:18:31] 然后不要实现任何的功能 [00:18:32] 可能是可能是更便宜的好 [00:18:37] 需求我是讲需求 [00:18:39] 那这样的一个系统 [00:18:41] 就是一个非常典型的这个业业务系统 [00:18:46] 比如说比如说我除了找到我的课表以外 [00:18:49] 我应该还可以找 [00:18:52] 对我收藏了 [00:18:54] Sorry [00:18:55] 比如说我的教学任务 [00:18:57] 对我的教学任务 [00:19:00] 你在这里可以看到大家的信息 [00:19:02] 我就不点了 [00:19:02] 因为点开以后好像会显示 [00:19:04] 电话号码还是什么的 [00:19:06] 这显然是一个比较糟糕的糟糕的feature [00:19:09] 那在学生名单里面可以看到看到电话号码 [00:19:12] 然后在这里上一次给大家演示过 [00:19:14] 直接用AI来用slop来填满教学周底 [00:19:17] 你看没有人管我 [00:19:19] 我没有任何人来审计 [00:19:21] 我填的任任何的任何的内容 [00:19:24] 至少至至少还没有 [00:19:25] 我觉得他们更更应该 [00:19:26] 把这个教室的灯给灯给修好好 [00:19:29] 然后你就看到在这个界面里面有无穷多的功能 [00:19:35] 你看比如说有编辑联系方式 [00:19:37] 然后我在这里有当前教学期 [00:19:39] 最新排课学期历史授课信息 [00:19:42] 然后在这个历史授课信息里面 [00:19:44] 我要选择学年学期诶 [00:19:48] 选择学年学期不太好用 [00:19:52] 你的感觉是不太好用又还蛮好用的 [00:19:55] 他还是做了需求 [00:19:58] 需求分析的 [00:19:59] 比如说期末考试签到表哎 [00:20:03] 这也是一个需求功能点 [00:20:05] 它在期末考试期间允许你点击这个按钮 [00:20:09] 在没有到期末考试发布的时候 [00:20:11] 你要弹出一个窗口 [00:20:13] 告诉你无法打印 [00:20:14] 然后再比如说编辑教材诶 [00:20:16] 编教材进入了另外一个页面 [00:20:18] 有一个弹出窗口哎 [00:20:23] 再试一次 [00:20:24] 编辑教材 [00:20:27] 嘶曾经好像是可以的 [00:20:30] 好我们已经成功发现了一个bug [00:20:33] 查看大纲 [00:20:35] 大纲是大纲说的教学安排 [00:20:38] 仅展示最新一个教学班 [00:20:42] 哦他等了很久才把这个页面刷新出来 [00:20:45] 这个可能不满足它的功能性需求的 [00:20:48] 要求状态正常 [00:20:50] 看有没有还有没有别的可以点的 [00:20:51] 我就不用A键的点 [00:20:52] 我还是用古法方式啊来点好 [00:21:00] 没问题 [00:21:00] 这是我的教学任务 [00:21:02] 然后和本科生相关的 [00:21:05] 还有很多功能助教 [00:21:09] 比如说我的助教 [00:21:10] 我这门课好像啊对我这门课没有助教 [00:21:14] 我能搜我的名字吗 [00:21:16] 不对这是学号 [00:21:18] 这个是学 [00:21:19] 这是我班上的助教 [00:21:22] 找不到课表内教学班没有助教诶 [00:21:25] 这是一这是什么 [00:21:28] 我这是这是什么 [00:21:33] 这是什么 [00:21:34] 让我瞧一瞧 [00:21:36] 咳咳诶 [00:21:37] 在这里我看到了一些JX [00:21:44] 太厉害了 [00:21:45] 教学班名称堪比堪比agent [00:21:49] 搜索哎就看到这个需求是不断叠加的 [00:21:53] 因为曾经是没有慕课这样的功能的 [00:21:57] 毫无疑问 [00:21:58] 在比如说在疫情之前是没有慕课功能的 [00:22:00] 然后在疫情的时候 [00:22:01] 领导说我们要我们需要为老师添加慕课 [00:22:05] 然后添加完慕课以后需要慕课的助教 [00:22:08] 然后在慕课的助教 [00:22:09] 不要把它当成普通教学班的助教 [00:22:11] 要把它当成慕课的助教 [00:22:12] 然后就有了这个哎这个是啧史诗级的bug [00:22:16] 我不知道我不知道为什么 [00:22:19] 但是这是一个史诗级的好吧 [00:22:21] 然后我点删除可以吗 [00:22:23] 好点新增可以吗 [00:22:25] 请在可维护时段内操作课内教学班可以吗 [00:22:28] 新增请在可维护 [00:22:31] 现在不能已经不能不能申请助教了 [00:22:33] anyway [00:22:34] 这就是你看到的教务系统 [00:22:38] 所以所以你就理解上上一页 [00:22:40] 我说我说曾经很难讲 [00:22:42] 你们不会去见真正的客户的 [00:22:43] 你们可能也不敢拍拍胸脯说 [00:22:47] 这个给我20万 [00:22:49] 这个我找几个同学 [00:22:51] 我们把这个南大的教务系统给包了 [00:22:53] 你承担不了这个责任 [00:22:55] 他到到时候跟你签一个合同 [00:22:57] 然后你说你如果逾期交付的时候 [00:23:00] 达不到功能要求 [00:23:01] 达不到非功能要求的时候 [00:23:02] 怎么办的时候 [00:23:03] 你就你就傻眼了 [00:23:05] 你当然你们作为一个成年人 [00:23:07] 你们是可以去成立自己公司 [00:23:09] 然后去和学校签订这样的协议 [00:23:11] 但是你们啊你们也不敢 [00:23:14] 我你们不敢不要紧 [00:23:16] agent他敢就行 [00:23:17] 所以我干了这样的一件事 [00:23:19] 我 [00:23:22] 我让GPT帮我就跟跟我在在这儿 [00:23:27] 用 PyAgent 说 [00:23:28] 我去爬一下 [00:23:29] 去帮我去生成一个 [00:23:32] 什么校服信箱的投稿一样 [00:23:34] 我让 GPT 去探索一下这个教务系统里面 [00:23:38] 和教师相关 [00:23:39] 注意只是教师相关的功能 [00:23:40] 然后得到一个需求说明书 [00:23:45] 好我们来看一看这个需求说明书还是很不错的 [00:23:48] 南京大学本科教学服务平台需求说明书 [00:23:52] 这就是一个真实的需求报告的案例 [00:23:56] 你们可以在软件工程的课上看到这样的案例 [00:23:59] 然后软件工程课上也可能会给你们看一些来自 [00:24:02] 比如说真实企业的 [00:24:03] 需求说明书的一部分 [00:24:05] 但这个就是真的 [00:24:06] 所以AI真的是很好用 [00:24:07] 你要学东西的时候 [00:24:09] 我就想如果要教需求工程的时候 [00:24:12] 我可以当然可以找一个经典的案例 [00:24:14] 那我也可以把身边的案例直接变成需 [00:24:17] 需求说明书 [00:24:18] 好 [00:24:19] 你马上就发现这个目录里面有这么多项 [00:24:24] 从调研方法 [00:24:26] 证据边界到业务目标 [00:24:28] 成功标准到各种各样的 [00:24:31] 详细用例规约功能需求目录 [00:24:33] 最后还有术语文档以与配套文件 [00:24:37] 所以你看 GPT 是懂软件工程的 [00:24:39] 他为什么要写这么多 [00:24:40] 这在我们看来 [00:24:42] 这已经是一个做好的系统 [00:24:43] 这就完全的slop [00:24:45] 就突然间写了好多好多的东西 [00:24:47] 但他懂得 [00:24:47] 如果你要做一个真正的工程 [00:24:49] 当你面对的是客户的时候 [00:24:51] 一个术语就很重要 [00:24:53] 在他看不懂的时候 [00:24:54] 他可以去查一下术语 [00:24:56] 他就对齐了 [00:24:57] 虽然你不是UML [00:24:58] 不是一个还在早期的意图沟通的阶段 [00:25:02] 但是这些东西显然是有一定用处的 [00:25:06] 好啊我就随便看一看 [00:25:09] 需求功能的目录你看他是有traceability的 [00:25:15] 每一个它遵循了一个比较好的软件工程的规范 [00:25:18] 里面的每一个需求 [00:25:20] 比如说需求点和 [00:25:21] 比如说前面是不是有叫角色 [00:25:24] 角色 [00:25:28] 参与者或者业务目标 [00:25:31] 他都有一个语义上的 [00:25:33] 有一个形式语义 [00:25:34] 然后这个形式语义可以帮你建立traceability [00:25:38] 而不是说我这个 [00:25:41] 教学班是和学生管理是有关的 [00:25:44] 你自然语言肯定是说了这样的一句话的 [00:25:46] 但是他用有编号一个ID有形式意义的ID [00:25:51] 它就A1定程度上的帮助你 [00:25:54] 做了traceability [00:25:56] 我们来看一看 [00:25:58] 看一看功能需求 [00:26:01] 门户与身份提供分类服务 [00:26:04] 检索和应用入口 [00:26:05] 当一个服务支支持多种已分配身份时 [00:26:09] 提供身份选择 [00:26:10] 并显示当前身份 [00:26:11] 你看当你真正做软件的时候 [00:26:13] 这复杂的就来了 [00:26:14] 你可以不说这些 [00:26:16] 这些你都不说 [00:26:17] 但是有和到底有还是没有 [00:26:21] 我们看一下 [00:26:23] 他应该是观察到了 [00:26:27] 身份选选择诶啊我就不登录了 [00:26:31] 刚才刚才在那个本科生 [00:26:35] 在我选选择教师课表还是哪一个界面的时候 [00:26:39] 确实看到了 [00:26:40] 他让我选 [00:26:40] 是反正我选了底下那个本科生教师 [00:26:43] 但在多种身份的时候 [00:26:47] 服务页面明确确当年学期 [00:26:50] 学年介词或业务批次允许在授权范围内切换 [00:26:53] 这也是一个 [00:26:54] 一个设计就在在意图阶段 [00:26:57] 你的领导想要 [00:26:59] 领导说这个或者说不光是领导的 [00:27:01] 还是一线的教师 [00:27:03] 说我打开这个页面以后 [00:27:04] 什么样的界面对我来说是比较舒适的 [00:27:07] 所以所以刚才你看到 [00:27:09] 教务系统还是有一定的设计的 [00:27:11] 设计感的 [00:27:12] 他认为你在做 [00:27:14] 他会想你做这件事情的时候会想要做什么 [00:27:17] 然后他会留一个combobox在那边 [00:27:19] 你可以选那个combobox [00:27:20] 然后选一个学期或者选一个 [00:27:22] 选一个什么东西好 [00:27:25] 教学周易课程与大纲教材选用对吧好 [00:27:29] 这你就知道了 [00:27:30] 这就是一个非常tedious的事情 [00:27:34] 然后这个事情tedious到你根本就不想干 [00:27:37] 因为你做这些事情对你获得学分 [00:27:42] 获得学分绩 [00:27:43] 获得长进 [00:27:44] 看起来没有什么这个本质的好处 [00:27:48] 你还不如去做一个 [00:27:49] 你没有之前没有想过的算法题 [00:27:52] 这样可以使你解决算法问题的能力高一点 [00:27:54] 使得你在最后保研机试的时候可以 [00:27:57] 比别人多拿一点分数 [00:27:59] 而你在这里面一个一个条目往后写 [00:28:02] 是非常就无以nonsense [00:28:04] 你自己不会想去写想写 [00:28:06] 但是就是量变引起质变 [00:28:08] 如果你这个需求文档 [00:28:10] 比如说你的课程的作业 [00:28:13] 也给你一个需求文档 [00:28:15] 你的数字逻辑电路课的作业 [00:28:16] 计算机系统基础课的作业 [00:28:18] 计算机系统基础课 [00:28:19] 你们写那个for system emulator的时候 [00:28:22] 挺好玩的 [00:28:23] 这是一个你们见到的第一个比较长的需求文档 [00:28:27] 我们在设计这门课的时候 [00:28:29] 在那之前OJ都是一个非常短的需求文档 [00:28:32] 明确给你输入接口是什么 [00:28:34] 输出接口是什么 [00:28:35] 而且那个输入输出的接口都很简单 [00:28:37] 一般都少输入一个整数 [00:28:38] 还是空格分隔的整数 [00:28:40] 你只要用c in就可以输入了 [00:28:43] 而你们在计算机系统基础课上 [00:28:45] 第一次见到一个比较长的specification的guide [00:28:49] 它的你我们首先需求的要求是 [00:28:51] 你要实现一个NEMU [00:28:53] 这个NMMU [00:28:54] 你的第一个live可能是实现一个调试器 [00:28:56] 这个调试器你不需要事先解释指令 [00:28:58] 第二个实验你需要实现指令的解释,好 [00:29:01] 这也是一句话 [00:29:02] 但是到底什么是指令 [00:29:04] 指令的行为写在文档里 [00:29:06] 你就得到了另外的一个更大的需求文档 [00:29:08] 所以其实当系统走到大到一定程度的时候 [00:29:14] 你们做的训练都是相通的 [00:29:16] 你在学这个计算机系统基础这门课的时候 [00:29:19] 其实一定程度上学了软件工程好 [00:29:24] 然后就是AI的魅力时刻了 [00:29:27] 我就我就不展开了 [00:29:28] 就是这是需求工程里面 [00:29:31] 你为了挣钱 [00:29:32] 你看这是验收场景 [00:29:34] 还有验收的场景 [00:29:35] 然后还有需求清单 [00:29:39] 一个非常好的一个结构化的一个需求清单 [00:29:42] 这个checklist最终你就可以把它分成 [00:29:45] 前20个需求分给这个这个团队 [00:29:47] 后 [00:29:48] 二手需求分给这个团队 [00:29:49] 然后就可以track每一个人的进度 [00:29:52] 你就知道哪一个团队落后于实现需求 [00:29:55] 落后于进度了 [00:29:56] 这个traceability有了以后 [00:29:58] 你作为一个管理者 [00:30:01] 你就可以把团队管起来了 [00:30:03] 这个当然这是我们本科 [00:30:06] 上课的时候不怎么学的 [00:30:11] AI的魅力时刻就在于你真的 [00:30:18] 我又干了这样的一件事 [00:30:21] 我让 GPT 起一个 sub agent [00:30:24] 然后这个sub agent不要看任何的其他的教务系统 [00:30:28] 只根据这个需求说明书来实现一个假的教务系统 [00:30:35] 然后就得到了一个哎 [00:30:37] 我觉得突然你又有信心了 [00:30:41] 突然你又有信心了 [00:30:44] 觉得好像我也可以 [00:30:48] 20万跟学校谈一个合同了 [00:30:50] 哼 [00:30:52] 哎真的还就是还跟我刚才看的那个挺像的嘞 [00:30:58] 返回到全部教学任务 [00:31:00] 我的教学任务 [00:31:01] 本科教材管理软件工程实践者的研究项方法 [00:31:06] 编辑记录 [00:31:07] 我看这能不能编辑 [00:31:08] 演示数目一保存记录 [00:31:11] 记录已保存 [00:31:13] 这个这个成功的保存了 [00:31:16] 学生名单 [00:31:20] 诶这是这GPT起名字的偏好 [00:31:24] 就他认为在这个我的教务系统里面里面 [00:31:28] 这些哎这家其实挺其其实挺挺文雅的 [00:31:32] 然后你又觉得他有自己的这个偏执 [00:31:35] 在在在在里面 [00:31:37] 诶这是什么一次记录缺课一次哎 [00:31:43] 他不给我输入一 [00:31:46] 那就啊保存记录诶 [00:31:52] 哇这个还是呵 [00:31:55] 那他没有阻止我 [00:31:57] 我记录一个这个这个其实也不合理 [00:31:59] 因为这个数字已经超过了 [00:32:02] 所有的这个学期上课次数的总和 [00:32:06] 但你可以看到的是软件工程的魅力时刻 [00:32:10] 当你的需求是清楚的时候 [00:32:14] 当当你的需求文档 [00:32:16] 已经写到那个那个程度的时候 [00:32:19] 那么编码的事情 [00:32:20] traceability的事情 [00:32:21] 测试的事情 [00:32:23] 其实AI都能帮你搞定了 [00:32:25] 你只需要一份需求需求就行了 [00:32:28] 所以这是需求工程 [00:32:31] 我们回一会还会回到再回回到回到这里 [00:32:33] 先给大家 [00:32:35] 看一下这个一个从入学到毕业的一个 [00:32:39] 教务系统 [00:32:40] 它包括了权限规则 [00:32:43] 版本数据同步操作留痕 [00:32:45] 各种各样的这样的一个一个功能 [00:32:47] 你就知道了 [00:32:49] 需求无非就是通过无尽的和客户的沟通 [00:32:54] 把客户想要什么给想清楚 [00:32:57] 小到包括你要在界面上面放 [00:33:00] 要不要放一个当前学期和学年 [00:33:03] 要不要在哪一个地方有一个修改的按钮 [00:33:06] 这些都要跟用户对齐 [00:33:08] 因为你的你的用户不是你自己 [00:33:12] 在你设计尤其是做需求的时候 [00:33:13] 这个是普遍的 [00:33:14] 我们可能computer [00:33:16] comcomputer science同学缺乏的那种软技能 [00:33:18] 你要有同理心 [00:33:20] 你不是说我是一个geek [00:33:21] 我就假设所有人都会写程序 [00:33:23] 然后你考虑的是那个教务员 [00:33:26] 那个教务员可能是本科学历 [00:33:29] 然后今年已经53岁 [00:33:32] 还有两年就要退休了 [00:33:34] 一个优雅的女士 [00:33:36] 那她在他这个上大学的时候 [00:33:42] 可能甚至都没有 [00:33:44] 没有操作过电脑 [00:33:45] 然后我要我们要设计一个教务系统 [00:33:47] 要为他设计 [00:33:49] 也要为同样为我设计 [00:33:51] 为年轻人设计 [00:33:53] 诶 [00:33:53] 这个时候 [00:33:55] 怎么得到那一张表 [00:33:57] 这是完全是另外的一个问题 [00:33:59] 当然这不是这门课的 [00:34:00] 不是这门课的课的重点 [00:34:03] 这门课重点是说 [00:34:05] 当你的教务系统已经像刚才那样 [00:34:09] 一个那么长的需求需求列表来做的话 [00:34:13] 其实就已经进入了泥潭了 [00:34:16] 就你刚才就刚才这里就说当你第一步的时候 [00:34:22] 或者这么讲吧 [00:34:22] 应该应该这么说 [00:34:24] 如果你真的要做一个教务系统 [00:34:26] 那你就是被迫的 [00:34:28] 你被迫要从零开始 [00:34:30] 当然你这就是行业的壁垒和经验 [00:34:32] 就如果如果我是一个行 [00:34:34] 有行业资质 [00:34:35] 或者说在这个行业已经摸爬滚打20年的公司 [00:34:37] 我有很多这个领域知识 [00:34:39] 我知道教务员想要什么 [00:34:42] 你作为一个毛头小伙子 [00:34:44] 本科生 [00:34:44] 你不知道这个教务员到底想要什么 [00:34:46] 所以你设计出来的那个软件 [00:34:47] 你做的那个需求很有可能就是有偏差的 [00:34:50] 但我有这个行业的经验 [00:34:51] 我设计出来的东西可能就哎 [00:34:53] 我对学位授予的流程比你清楚 [00:34:56] 我就知道这个流程应该怎么样 [00:34:59] 怎么样变成这个需求的文档列表 [00:35:02] 但某种程度上我做软件都是被迫的 [00:35:04] 在刚开始的时候 [00:35:05] 一个空项目的时候 [00:35:06] 没有任何东西是漂移的 [00:35:08] 但当你一旦写出了一个需求列表的时候 [00:35:13] 马上AI不管是AI还是人类就给你实现了一版 [00:35:18] 就像刚才我们看到的那个教务系统一样 [00:35:20] 那实现那版以后 [00:35:22] 这个你就背上了技术债 [00:35:26] 因为我们的定律是需求永远不可能说清楚 [00:35:32] 第一就是 [00:35:32] 你永远不可能真的把用户的需求说清楚 [00:35:35] 其次需求不是一成不变的 [00:35:39] 我这边AI帮我虚构的一个案例 [00:35:43] 我大概这个我能体会这个案例的 [00:35:48] 实际可能会发生 [00:35:49] 这真的是可能在学校里发生的 [00:35:51] 比如说我们干了这样的一件事 [00:35:53] 我们把离散数学课拆开了 [00:35:54] 这是你们知道的 [00:35:55] 我们觉得大家就第一门课的那个离散数学 [00:36:00] 没有教会大家那个数数学的语言 [00:36:02] 所以我把它拆开了 [00:36:03] 然后拆开以后就带来一个非常糟糕的后果 [00:36:07] 我们的准出结果变了 [00:36:11] 我们的保研要求也变了 [00:36:14] 然后新旧是衔接不上的 [00:36:17] 因为以前以前的系统里面 [00:36:20] 比如说我们要求好 [00:36:22] 在以前系统里面定义 [00:36:24] 我们要求计算机系的学生 [00:36:27] 或者计算机学院的学生 [00:36:29] 如果要毕业 [00:36:30] 那就必须要选修离散数学 [00:36:32] 合理好 [00:36:34] 那这个离散数学现在从上学期一门课 [00:36:40] 变成了上下学期两门课 [00:36:43] 那怎么办 [00:36:45] 我是不是要在系统里面增加一个规则 [00:36:47] 说我允许毕业 [00:36:50] 要么选修了离散数学 [00:36:52] 要么选修了离散数学一 [00:36:55] 这就一个或的关系 [00:36:57] 然后最早的时候 [00:36:59] 第一版本在沟通这个需求的时候 [00:37:01] 肯定没有想过这件事 [00:37:04] 系统里没有 [00:37:05] 系统里没有的时候 [00:37:07] 当他写这个需求的checklist的时候 [00:37:09] 他会说什么 [00:37:10] 好我们要在一个界面上面有一个叫毕业要求 [00:37:15] 就毕业要求好 [00:37:18] 然后在毕业要求上面我们要给教务员 [00:37:21] 这个可能是应该是教务员对 [00:37:24] 教务员有一个下拉框 [00:37:26] 一个增加一条的按钮 [00:37:29] 然后这个增加一条的按钮可以选一个 [00:37:31] 选一个课程 [00:37:35] 选一个课程好 [00:37:35] 这是一个非常在那个时候 [00:37:37] 就在在固定到刚开始做需求的那个时间点 [00:37:40] 这个当然是非常合理的需求 [00:37:43] 但是一旦这个需求固定下来 [00:37:48] 当我们的离散数学课拆成222份了 [00:37:51] 好完蛋了 [00:37:53] 这个东西不能适应我们新家的需求好 [00:37:57] 那就对不起 [00:37:58] 重构吧 [00:37:58] 我们再把这个需求的文档调出来 [00:38:01] 这个地方不能这样了 [00:38:02] 我要在这里加一个或 [00:38:05] 下拉框里加一个货 [00:38:07] 然后每当你叠加一个这个东西的时候 [00:38:09] 这个屎山就开始累积了 [00:38:10] 你们就 [00:38:11] 写过软件的都有都有 [00:38:13] 这个都懂这个你不要紧 [00:38:15] 你让我做个东西好吧 [00:38:17] 改需求真的受不了 [00:38:19] 因为改需求它会破坏 [00:38:23] 你做了一个整体上一致的设计 [00:38:26] 但是一旦这个需求中间产生了一个裂缝以后 [00:38:30] 它会传递到系统里的各个部分 [00:38:33] 然后当你一个看似很小的需求要改的时候 [00:38:36] 牵动了整个系统的各个 [00:38:38] 各个部分的时候 [00:38:40] 你就一团一团乱麻对 [00:38:44] 这里面举了更多的例子 [00:38:46] 什么重修 [00:38:47] 然后还可以交换 [00:38:48] 可以抵扣 [00:38:49] 也就有些课程可以抵扣 [00:38:50] 然后这里面带来的 [00:38:52] 如果有一个有一个同学从别的院系转过来 [00:38:59] 然后别的院系又有一个离散数学课 [00:39:03] 软院好像还是这个一一门的E3数学课 [00:39:07] 然后这时候这门课能不能抵扣 [00:39:11] 计算机学院的离散数学 [00:39:13] 他应该抵扣计算机学院的离散数学一和二 [00:39:17] 还是我要求我抵扣计算机学院的离散数学一 [00:39:22] 你还要再上一个离散数学二 [00:39:24] 这就无穷多 [00:39:26] 你一旦面对这个开放的物理世界的时候 [00:39:29] 你就没办法了 [00:39:30] 而且1万个1万 [00:39:32] 最后到最后你的系统里没有的东西 [00:39:34] 还是可以暗箱操作的 [00:39:36] 就最终还是有个override的 [00:39:38] 因为比如说你今年6年了 [00:39:40] 拿不到本科本科学位了 [00:39:42] 就是因为离散数学这门课 [00:39:43] 然后刚好你在这个时候碰到了 [00:39:46] 我把离散数学课拆成两门 [00:39:47] 那怎么办 [00:39:49] 你也没有办法 [00:39:50] 就现在已经是秋季学期了 [00:39:51] 你啊或者说现在已经春季学期了 [00:39:53] 马上要毕业了 [00:39:54] 你已经没有 [00:39:55] 不可能再去修那个春季学期那个课了 [00:39:57] 他不开 [00:39:57] 他在春季学期开 [00:39:59] 那怎么办 [00:40:00] 怎么办 [00:40:01] 最后领导说我认为这个教学主任说 [00:40:06] 我认为这两门课之间是存 [00:40:08] 可以存在替代关系的好 [00:40:10] 那这个系统里面最终怎么办 [00:40:15] 最终怎么办 [00:40:15] 最终好 [00:40:17] 打电话给工程师这个数据库里面这个好 [00:40:21] 这个这个这个同学他的毕业条件 [00:40:23] 你把它改成满足 [00:40:25] 然后这个就过去了 [00:40:27] 这个还是档案可查的 [00:40:28] 就是就最终这个层层的这个审批流程都会在 [00:40:31] 就是最终是教学院长认为 [00:40:32] 这个同学了这个旧的这项数学 [00:40:35] 还是可以可以抵扣这个新的离散数学的 [00:40:38] 也合理 [00:40:38] 就是在人类世界意义上有一个合理的解释 [00:40:41] 所以这就是软件系统的复杂 [00:40:43] 你任何时候这个实现需求就做了 [00:40:48] 一旦你做了需求分析 [00:40:50] 你就背上了技术债 [00:40:51] 我记得我开始上课的第一天 [00:40:54] 那应该是2018年的时候 [00:40:55] 好多年以前了 [00:40:57] 那个时候还是一个老版的教务系统 [00:40:59] 好像是可和我上学的时候教务系统差不多 [00:41:03] 然后上课第一天我就发现诶 [00:41:05] 这个系统我没有用过 [00:41:06] 因为我以前都是学生端 [00:41:08] 我第一次用老师端 [00:41:09] 然后发现很好玩 [00:41:10] 可以给同学设置缺课次数 [00:41:12] 然后我不知道为什么要做这个需求 [00:41:14] 就是就如果是我来做这个需求的话 [00:41:17] 我就我就我就不要把缺课次数 [00:41:19] 这个feature放在我的教务教务系统里面 [00:41:21] 同学缺课就缺课吧 [00:41:23] 缺课缺课 [00:41:23] 你留存任何一个证据 [00:41:25] 就你不需要在系统里留证据 [00:41:26] 只要老师比如说有一个签到表 [00:41:28] 点名表留证据 [00:41:29] 然后扫扫描一下 [00:41:30] 我就可以把你开除了 [00:41:31] 你们你们学生手册里面有写的 [00:41:33] 你们缺课达到多少个 [00:41:36] 无故无故缺课达到多少次 [00:41:37] 就可以予以开除了 [00:41:42] 然后我就试着绕过了那个前端的校验 [00:41:44] 就刚才刚才你看那个GP [00:41:46] 实现那个质量还是不错的 [00:41:47] 我相信他前端后端也都会做校验 [00:41:49] 对啊我要我要输入一个非常大的缺课次数 [00:41:51] 他直接拒绝拒绝我了 [00:41:53] 我把一位同学的缺课次数从零改成了一 [00:41:57] 这好像也不是什么有害的操作 [00:42:00] 然后但是立马这个所有的就我发现所有的 [00:42:04] 只要能够选出这位同学 [00:42:06] 这个选课记录的这个页面就全部crash了 [00:42:08] 直接500就你就crash了 [00:42:11] 然后然后我就只好打打电话给教务 [00:42:13] 说哎呀这个教务系统这个崩溃了 [00:42:15] 然后我不知道他怎么解决 [00:42:16] 反正第二天可能工程工程师哼 [00:42:19] 登录到了那个数据库里面 [00:42:20] 然后把那个一的条目给修修复了 [00:42:23] 然后我就知道再也就这就是软件系统 [00:42:26] 你们真实面临的软件系统的的需求 [00:42:30] 所以这意味着什么 [00:42:31] 这意味着就只是需求只是一个lesson [00:42:34] 因为我其实没有办法在这门课上面讲 [00:42:36] 需求和啊和和产品设 [00:42:39] 设计这个可能应该是另外一门产品课 [00:42:42] 我这门课讲软件工程课不适合 [00:42:45] 可能就这样带过比较合适 [00:42:48] 就AI时代 [00:42:49] 你做MVP没问题 [00:42:52] 正确 [00:42:53] 而且是前所未有的容易 [00:42:55] 你先优先实现那个最重要的功能 [00:42:57] 你看到一个产品 [00:42:58] 你马上就知道哪个地方哎呀设计错了 [00:43:00] 你这个时候推导的成本是比较比较低的 [00:43:05] 但是这也给你产生了一个驾驭AI的错觉 [00:43:09] 因为AI确实可以往后修 [00:43:12] 就是你迭代三个版本 [00:43:13] AI都是可以往后修的 [00:43:14] 但一旦你在一个真实的系统里面 [00:43:17] 运行足够长的时间 [00:43:20] 比如说计算机系改成了计算机学院 [00:43:21] 当你一开始面对这些事的时候 [00:43:23] 你原来的设计不起作用的时候 [00:43:26] 好你就 [00:43:28] 你就面临面临麻烦了 [00:43:30] 这也是所有的软件工程都面临的问题 [00:43:32] 叫there is NO silver bullet [00:43:34] 没有银弹 [00:43:35] 所谓的银弹是 [00:43:37] 就是吸血鬼和狼人是没有办法普通杀死的 [00:43:41] 但是要有一个silver bro [00:43:43] bullet [00:43:44] 但是没没有没有银弹 [00:43:46] 这个软件工程里 [00:43:48] 随着软件的增长 [00:43:51] 你在最早的时候 [00:43:53] 你制定下来的那些就在第一个时刻制定下来 [00:43:56] 那些决策会不断的 [00:43:58] 因为你的软件要生存 [00:43:59] 要挣钱 [00:44:00] 就假设这个软件不是你 [00:44:02] 你课程上面 [00:44:03] 我随时可以把它清零 [00:44:04] 从零从头再做一遍 [00:44:06] 你这个软件一旦上线 [00:44:07] 他就开始和物理世界产生产生交火 [00:44:10] 那你就必须持续的背着这个technical det维护 [00:44:14] 维护下去 [00:44:14] 那它的成本就会因为你前面做的不够前瞻 [00:44:18] 不断的啊变得更糟糕 [00:44:21] 对啊这是需求 [00:44:23] 那解决这个问题的方式是什么 [00:44:28] 是什么 [00:44:29] 啊是架构 [00:44:31] 其实刚才我已经有一点点透露了 [00:44:36] 我们怎么怎么应对这个问 [00:44:39] 应对这个问题 [00:44:39] 当你刚开始的时候 [00:44:41] 你可能有一个需求 [00:44:43] 或者是在MVP的阶段 [00:44:45] 你还没有试图把所有要做的事情都放进来 [00:44:48] 你可以想一想 [00:44:49] 我们的软件第一步迈向哪里 [00:44:53] 是对这个软件的长期生存有最好的效果的 [00:44:58] 我们的第一步 [00:44:59] 而第一步你要做的实际上是软件的架构 [00:45:03] 不仅仅是一个MVP [00:45:05] MVP就刚才刚才我们看的那个slop [00:45:08] 那个AI生成的那个MVP [00:45:10] 那已经不能算MVP了 [00:45:11] 这已经算是一个小小小型的产品原型 [00:45:15] 原形了 [00:45:17] 维护它会变得非常困难 [00:45:19] 你想要驾驭这个软件系统的复杂性 [00:45:22] 你在一开始的时候要想 [00:45:23] 我怎么样做一个最小的软件 [00:45:26] 使得这个软件能够随着需求的膨胀和改变 [00:45:31] 它依然能够一定程度上有一点韧性 [00:45:35] 实际上是最哪怕是最小的代码 [00:45:38] 它都是有软件的架构的 [00:45:42] 然后这里面说白了就一个把耦合解开 [00:45:47] 就是你不要用 [00:45:48] 比如说就说一开始的话 [00:45:49] 我用一个数据库来存放所有的需求 [00:45:52] 如果你在一开始的时候就立即做这个决定 [00:45:54] 你可以快速得到一个能work的原型 [00:45:56] AI也能很快给你做出来 [00:45:58] 但是如果你不去仔细思考 [00:46:00] 这个系统里面到底哪些概念是可以分开的 [00:46:04] 确实很容易进入啊软件维护的泥潭 [00:46:09] 好,讲到架构 [00:46:10] 大家都觉得有玄学 [00:46:13] 好像很高大上 [00:46:15] 但其实不是的架构 [00:46:16] 从你在第一次开始设计任何的代码 [00:46:21] 任何的系统的时候 [00:46:22] 无论它多小 [00:46:24] 它里面都有一种这种架构的思维 [00:46:26] 因为架构本 [00:46:27] 本质上就是怎么样去控制一个东西的复杂性 [00:46:31] 而当你的代码开始稍微哪怕有一点点的规模 [00:46:36] 你都可以去考虑这个复杂性应该怎么样去分解 [00:46:40] 比如说 [00:46:42] 我不知道你们写OJ的时候会不会这样写 [00:46:45] 写一个main [00:46:46] 然后read input [00:46:47] parse input [00:46:48] Process [00:46:48] input上 [00:46:49] Ride output [00:46:50] 你们会这样写吗 [00:46:51] 不知道 [00:46:52] 然后你们会看到 [00:46:55] 如果你要做一个命令行的程序 [00:46:58] 比如说你要写一个main [00:47:01] 然后这个main是有这个argc 和 argv的 [00:47:07] 这是操作系统和进程之间的约定 [00:47:10] 他会他会传一个r vector of arguments [00:47:13] 把参数与字符串的形式传过来 [00:47:15] 那你们怎么处理参数 [00:47:16] 一个string compare吗 [00:47:20] 你就想 [00:47:20] 如果你今天在保研的那个集市的现场 [00:47:23] 那个题目要求你处理这个argv [00:47:26] 你会string compare吗 [00:47:28] 然后你们就会回想起这个世界上有一个东西叫 [00:47:34] 叫getopt [00:47:37] 然后你再课上看过这样的代码 [00:47:41] 你在做实验的时候写过这样的代码 [00:47:43] 然后你在别人的实际的项目里面 [00:47:46] 比如说你们感兴趣 Redis是怎么实现的 [00:47:49] Redis这些东西是怎么实现的 [00:47:51] 你会看到看到这样的一个代码 [00:47:53] 它也是一个架构 [00:47:55] 它也是一个分解复杂性的方法 [00:47:58] 因为有复杂性 [00:47:59] 这个argv 和 argc [00:48:02] 本身就是一个非常复杂的东西 [00:48:04] 比如说 [00:48:07] 我NPM的help不 [00:48:09] 刚才我是npm run def好 [00:48:11] 它本身就有很多 [00:48:13] 然后有些还是支持这个长长参数 [00:48:19] 有UNIX风格的长参数 [00:48:21] 短短参数 [00:48:22] 然后在C的时代 [00:48:25] 你要写一个这个parser是很复杂的 [00:48:28] 这里就有个架构 [00:48:29] 你在这里可以getopt [00:48:32] 然后他就把这个复杂性给隔开了 [00:48:36] 这个argv它有一组约定的 [00:48:39] 实际上是这个约定是有标准 [00:48:41] 是有个叫POSIX标准的 [00:48:43] 然后在这个post标准上 [00:48:44] 它就可以用getopt来处理 [00:48:47] 然后最后你只要写一个switch case [00:48:48] 然后一个while循环好 [00:48:51] 你就可以解析这里的参数了 [00:48:53] 你就学会了一种这种parser的这样的一个 [00:48:56] 架构的模式 [00:48:58] 然后当然这只是一个非常非常小的小小的例子 [00:49:02] 你在看到任何一个好的 [00:49:04] 符合best practice的代码的时候 [00:49:06] 它背后都有架构的设计 [00:49:08] 然后背后都映射到需求 [00:49:10] 所以我说需求虽然需求 [00:49:12] 刚才我讲那个教务系统有点有点过度了 [00:49:14] 你在做任何事的时候 [00:49:17] 做任何软件工程行为的时候 [00:49:18] 其实都有intent的specification和implementation [00:49:21] 不是要一个教务系统 [00:49:23] 那种monolithic的一个这样的 [00:49:25] 在随时随地 [00:49:27] 在一个模块里你都可以谈需求 [00:49:29] 都可以谈架构 [00:49:31] 都可以谈优雅 [00:49:32] 所以我很讨厌那种这个老登的那种语言 [00:49:35] 律师的授课方式 [00:49:36] 曾经 [00:49:38] 曾经我和这个学院的老教师 [00:49:40] 有这个比较大的分歧 [00:49:43] 然后比如说我们说我们要讲这个C++ [00:49:46] 相对来说至少在11 [00:49:48] 十四十七里面的一些 [00:49:50] 如果你要讲C++的话 [00:49:51] 那可能是在这pre-AI时代了 [00:49:54] 这些特性以及他们应该怎么用 [00:49:56] 然后这个老登的回应是说我讲了 [00:49:59] 我怎么没讲 [00:50:00] 然后他讲什么 [00:50:01] 他讲好 [00:50:03] lambda这个AB等于返回return a加B没有了 [00:50:07] 然后这个讲了和没讲有什么区别 [00:50:10] 其实你在学这个 [00:50:12] 就这你们在学习的时候 [00:50:14] 你看到的每一个东西 [00:50:16] 我说不管是你在GITHUB上面找到一个好的项目 [00:50:18] Read me [00:50:19] 还是你就看到一个getopt [00:50:21] 如果你再往后想一想 [00:50:23] 诶这确实是一种模式 [00:50:25] 这个模式这种parsing 的模式 [00:50:27] 就把一个复杂的一个字符串 [00:50:29] 最后变成一个简单的一个状 [00:50:31] 一个一个parsing state [00:50:36] 它就是一种抵抗需求变化的方式 [00:50:39] 因为你的argv是可以变化的 [00:50:41] argv各种各样的变化 [00:50:43] 但是他用这个parsing state把这个问题给解决了 [00:50:49] 所以我们可以看几个比较典型的架构 [00:50:51] 我从最小的十行代码的架构来开始讲 [00:50:56] 然后架构 [00:50:58] 就为为什么我们现在说好大模型 [00:51:00] 对架构不掌控的还不是很好 [00:51:03] 或者说架构是一种品位 [00:51:05] 其实是因为架构和需求其实是连在一起的 [00:51:09] 你没有需求就不用谈谈架构了 [00:51:11] 当你当你谈需求和架构的时候 [00:51:14] 你的需求面对的是一个开放的 [00:51:16] 就是为未来的需求做的一个架构 [00:51:20] 那你什么样的时候能设计出好的架构 [00:51:22] 就是你对需求掌握到什么程度 [00:51:25] 举个例子就是我最喜欢的Tower of Hanoi [00:51:28] 非地位的Tower of Hanoi [00:51:30] 我曾经也是在 [00:51:33] 就我以前开始上课的时候 [00:51:34] 我先问我的研究生 [00:51:36] 保研的研究生说 [00:51:37] 哎你让你写个非递归的这个Tower of Hanoi [00:51:40] 你能写出来吗 [00:51:40] 然后研究生摇头 [00:51:42] 这个 [00:51:44] 所以所以最后就变成了我上课的时候的一个 [00:51:47] 一个例子 [00:51:47] 然后其实就这个十行的代码 [00:51:51] 它背后的架构联系到大家 [00:51:54] 为什么写不出来 [00:51:58] 为为什么写一个非递归的汉诺塔 [00:52:00] 那么困难 [00:52:01] 那我们来看一看 [00:52:03] 现在的人和就人和模型对这个架构 [00:52:07] 就比如说我要做一个非递归的汉诺塔 [00:52:09] 我就给他一个problem [00:52:09] 他说我要写hanoi [00:52:10] 他们好多人你们来猜一猜GPT 又写了什么 [00:52:13] 这个即便说我不做人类了 [00:52:17] 我就你写不出来 [00:52:19] 是因为你的数学不如我好 [00:52:21] 然后他找到了这个汉诺塔的这个数学性质 [00:52:24] 所以很明显的GPT-6是一个对数学 [00:52:27] 就他也就AI 在数学道路上狂奔 [00:52:29] 然后希望他在数学上面的能力 [00:52:31] 能够拯救人类世界的一切 [00:52:33] 然后他就开始写这个 [00:52:35] 反正这是对的 [00:52:36] 他说盘号等于一加上步号 [00:52:38] 二进制末尾零的个数 [00:52:39] 每个盘子的移动方向 [00:52:42] 这还是一个移动方向固定吗 [00:52:44] We don't know [00:52:44] 反正anyway [00:52:45] 他这个应该是对的 [00:52:46] 反正你看不明白 [00:52:47] 但是highly optimized极度最优 [00:52:51] 当然我没有 [00:52:52] 除非我叫它模拟栈好 [00:52:54] 同样的prompt这个DeepSeek v4flash就更有人感 [00:53:00] 他写了个非常优雅的代码 [00:53:02] 就是标准答案 [00:53:03] 基本上如果你要写一个非递归的这个 [00:53:07] 汉诺塔的话 [00:53:08] 这是一个非常干净的架构 [00:53:10] 因为他知道我们的recursion递归 [00:53:14] 当我说要把一些盘子移过去的时候 [00:53:16] 那我要把这个剩下的任务一定要留在留在站 [00:53:19] 就还要继续执行的任务 [00:53:21] 留在站里 [00:53:21] 这样我总是执行栈顶的任务 [00:53:24] 往前所以非常优雅的优雅的事情 [00:53:26] 然后然后我特意试了一下豆包 [00:53:28] 然后豆包写了一个错的 [00:53:29] 然后我就呵呵 [00:53:30] 我就呵 [00:53:31] 我就不展示了 [00:53:32] 豆包就豆包选了这个方案 [00:53:34] 但是写了一个写了一个有瑕疵的代码 [00:53:38] 跟这个基本上差不多 [00:53:39] 我就不展示了 [00:53:41] 然后我就不一样 [00:53:42] 我作为一个架构师 [00:53:45] 架构关乎的是你对需求掌握到什么程度 [00:53:48] 所以我要写一个非递归的汉诺塔 [00:53:52] 我想的是为什么大家写不出来 [00:53:56] 大家写不出来的时候 [00:53:57] 是因为你对递归这件事情理解的不够深 [00:54:01] 所以我故意的把 [00:54:03] 上过我课的同学都见过这个代码 [00:54:05] 我故意说 [00:54:08] 我们的程序实际上是一个状态机 [00:54:10] 回头可以回头 [00:54:11] 你们可以去看我操作系统讲的这个课 [00:54:13] 然后任何一个就是C语言 [00:54:15] 是一个我花了一整节课来讲 [00:54:17] 这个C语言是一个C语言的状态的 [00:54:20] 然后这个状态一个single step是它的这个operational semantics [00:54:24] 叫操作语义 [00:54:25] 然后他的一个小步语义 [00:54:27] 比如说一个hanoi(n-1) [00:54:31] 它是一个well defined的 [00:54:33] 在程序语言世界上是well defined的一步操作 [00:54:36] 所以我们只需要模拟这个 [00:54:38] 我可以写一个C语言的解释器 [00:54:40] 当你理解了C语言的解释器的时候 [00:54:42] 你就很容易写出把任何代码 [00:54:44] 我当时讲的其实是两个函数 [00:54:46] 互相F 调 G、G 调 F的 [00:54:47] 你非常容易没有任何难度 [00:54:49] 因为你就是把这个C语言的形式语义 [00:54:51] 小步语义翻译成一个状态机的表示 [00:54:53] 所以就是这个 [00:54:54] 然后你可以把它翻译成它 [00:54:56] 就如果你觉得如果你觉得这个 [00:55:00] 如果你觉得这个代码不够优雅 [00:55:02] 对于Tower of Hanoi来说 [00:55:04] overkill了 [00:55:05] 因为我可以把任意的程序翻译 [00:55:07] 你还可以把它变成这个 [00:55:08] 他们俩是等价的 [00:55:09] 对 [00:55:10] 做一个simplification [00:55:11] 做个优化就可以从它变变成它 [00:55:13] 所以我从我来看 [00:55:14] 我作为我的架构师 [00:55:16] 这是理解C语言形式语义的好机会 [00:55:18] 所以看到不同的人 [00:55:19] 就我现在现在我还可以守住我的品位 [00:55:22] 我说这个GPT可能他其实他懂 [00:55:24] 但是他可能不会往这个方向去想 [00:55:26] 所以我觉得就我有比较强的信心 [00:55:30] 认为强化学习 [00:55:32] 可以把这个 GPT 往这个方向去引导 [00:55:35] 他 [00:55:35] 一旦学会他也能 [00:55:37] 做出在适当的时候会做出这样的这样的实现 [00:55:40] 当然最后有个搞笑的 [00:55:42] 我在百度上搜索了非递归汉诺塔 [00:55:46] 然后找到了第一个这个 [00:55:48] 而且百度很搞笑 [00:55:49] 百度 [00:55:50] 百度第一个第一个条目还是递归汉诺塔 [00:55:53] 然后我往下找到CSDN的 [00:55:55] 第一个非递归汉诺塔 [00:55:56] 然后我看到了这样的一个代码 [00:56:00] 啧这个我甚至比AI slop [00:56:04] 就它甚至比AI slop还要问题还要严重 [00:56:08] 就这里有一个move [00:56:10] 但是STD是有move的 [00:56:12] 哦对哈他这个然然后在右边 [00:56:20] 我不知道是不是不是AI是不是评论 [00:56:23] 那是不是AI [00:56:25] 也给你营造一种这个CSDN的博客 [00:56:29] 质量很高的感觉 [00:56:30] 但如果你们在上学的时候被这种 slop [00:56:33] 污染了 [00:56:35] 就是你还不如跟跟AI学 [00:56:37] 跟DeepSeek学都很好 [00:56:39] 不要GPT-6已经不当不当人了对 [00:56:43] 所以看同一个汉诺塔四种表达 [00:56:46] 我们说我们要控制的是复杂性 [00:56:49] 它是一个架构 [00:56:51] 也关乎抽象 [00:56:52] 然后我们站在不同的层面上考虑这个问题 [00:56:56] 叫跳出问题看问题 [00:56:58] 那gt跳gt也跳出问题看问题了 [00:57:02] 一切都是数学规律 [00:57:04] 我只要把这个里面 [00:57:05] 程序世界里面的数学规律理解清楚 [00:57:07] 那没有任何问题是我解决不了的 [00:57:09] 我服 [00:57:10] 然后deep seek做了一个 [00:57:13] 这是一个人类反复说 [00:57:14] 递归是栈 [00:57:15] 递归是栈 [00:57:15] 递归是战 [00:57:16] 他在语料里面学到了这一点 [00:57:17] 做了一个正确的事情 [00:57:19] 然后我往外走了一步 [00:57:20] 是你们对编程语言是世界的理解 [00:57:24] 投射成了递归和非递归的区别 [00:57:26] 然后再投射成啊这样的一份代码 [00:57:29] 那这个不知道是什么 [00:57:30] 这个这个就这就属于是训练还没有收敛 [00:57:33] 我觉得这训练还没有收敛的 [00:57:35] 这个很有意思吧 [00:57:36] 就是你在任何一个小的地方 [00:57:39] 都能看出架构水平的高低 [00:57:43] 这是十行的十行的架构 [00:57:46] 然后如果我们要再大一点 [00:57:49] 比如说2000行的架构 [00:57:51] 我们展示跳出问题看问题 [00:57:53] 代码之外有客户的规律 [00:57:55] 客户之外有社会的规律 [00:57:57] 社会之外有宇宙的规律 [00:57:59] 你们肯定想问架构的能力从哪来 [00:58:03] 从你学过的每一个东西上来 [00:58:06] 如果它是好的 [00:58:07] 它是优雅的 [00:58:07] 他促使了你对这个问题的思考 [00:58:11] 压缩就是智能 [00:58:12] 你把它压缩了 [00:58:14] 那你的那个first principle [00:58:15] 你总是从first principle去推导诶应该怎么样 [00:58:20] 所以其实这些能力就是你在学校里面 [00:58:21] 学习社会上面的积累 [00:58:23] 共同赋予了你的啊这个能力 [00:58:26] 比如说你必须要和除了教科书 [00:58:30] 除了做题之外的人打交道 [00:58:32] 你才能知道一个 [00:58:35] 马上就要退休了 [00:58:36] 一个优雅的这个教务老师 [00:58:39] 他是怎么看待教务系统的 [00:58:41] 他的想法可能和你的想法不一样 [00:58:43] 你的想法总是最好的 [00:58:44] 数据库最高的性能 [00:58:46] Whatsoever [00:58:46] 但他来说他只是想要那个字大一点 [00:58:48] 他能够点鼠标的时候 [00:58:50] 不要手滑到那个按边上那个按钮上 [00:58:54] 共情别人的能力 [00:58:57] 然后2000行2000行 [00:58:59] 我讲一个我上课时候讲过的例子 [00:59:01] qr code generator就是二维码生成 [00:59:04] 我上课的时候讲了一个Excel生成二维码的例子 [00:59:07] 我要打开它一下吗 [00:59:08] 我可以打开它看一下 [00:59:11] 应该是第一次课的时候 [00:59:27] 对就生成这样的一个可以扫描的二维码 [00:59:31] 然后他可以把任何一个一个长的字符串 [00:59:35] 然后编码编码成一个你能够直接扫描的 [00:59:40] 二维码 [00:59:41] 然后你回头再用软件工程的 [00:59:44] 就用我们今天讲过的概念去理理解这个问题 [00:59:47] 我们的需求 [00:59:50] 这是一个 [00:59:51] 这是一个和教务系统不太一样的例子 [00:59:54] 他的需求是非常明确的 [00:59:57] 需求因为需需求就是Excel的一个一个单元格 [01:00:00] 就我希望在这个Excel里面对 [01:00:05] 有生成一个零一序列的单元格 [01:00:07] 然后他有一个输入的单元格 [01:00:09] 它有一些输出的单元格 [01:00:11] 就非常非常deterministic的 [01:00:14] 任务 [01:00:15] 但是这里面是有架构的考虑的 [01:00:19] 你的Excel的公式呵 [01:00:22] 就我当然agent其实还挺好玩的 [01:00:25] agent对自己的能力到底到什么程度是有估计的 [01:00:29] 所以agent会就当这个任务足够困难的时候 [01:00:33] 他就会不做了 [01:00:34] 就如果你让agent出来给我给我生成一个 [01:00:37] 比如说你有两个两个sheet [01:00:39] 然后你要有一个第二个工作表里面 [01:00:42] 是我的学生的实验成绩 [01:00:45] 然后第一个工作表里面是我誊好了的 [01:00:47] 学生的期末考试成绩 [01:00:48] 然后我说诶给我写个公式 [01:00:49] 把那个学号对应的那个实验成绩给我拉过来 [01:00:55] 那这个是在他能力范围内 [01:00:57] 他就会一把梭 [01:00:58] 然后这个事情显然超过超出他的能力范围 [01:01:00] 他还是会分成就试图去分解这个问题 [01:01:04] 它也会做架构 [01:01:05] 但你看到 [01:01:06] 至少今天的模型在做架构这件事上 [01:01:09] 稍稍是有一些落后的 [01:01:11] 然后这里你就面对一个问题 [01:01:14] 如果啊如果你直接把它写成一个公式 [01:01:19] 你就会得到一个有 [01:01:22] 对,笑话说我们偷了一个 Lisp 程序 [01:01:27] 然后看到最后 [01:01:28] 然后看到最后一页是这个对 [01:01:32] 就因为这个公式实在是太长了 [01:01:34] 因为Excel的计算比较有限 [01:01:37] 它你只能用 [01:01:38] 就好比是让你们用那种什么 Brainfuck 去 [01:01:42] 实现一个 [01:01:44] 一个nontrivial的程序 [01:01:46] 什么毁灭战士的什么游戏 [01:01:48] 那你就写出来程序就会是就会是这样的 [01:01:51] 那这个时候你就需要一个一个抽象 [01:01:53] 而且而且我觉得AI之前我看到那两个版本 [01:01:57] 他没有考虑到的是这个需求看起来明确 [01:02:02] 但其实有很多坑 [01:02:05] 比如说Excel是有版本的 [01:02:08] 我们知道Excel从20192023 [01:02:12] 它不断的都在增加新的功能 [01:02:15] 比如说 [01:02:16] 我刚才讲的那个需求叫去查找学号对应的成绩 [01:02:21] 以前是一个叫VLOOKUP的函数 [01:02:23] 有没有同学知道吗 [01:02:25] 没有同学知道 [01:02:26] 说明你们都不是Excel的power user [01:02:28] 太好了 [01:02:28] 你们是agent时代的第一类人 [01:02:30] 你要干什么 [01:02:30] 只要跟agent说就行了 [01:02:31] 你永远永远不需要再学一个 [01:02:33] 像吃屎一样的语言了 [01:02:35] 然后微软大概终于在二零 [01:02:37] 可能是一九还是二四 [01:02:38] 反正在某一年的时候终于终于受不了了 [01:02:40] 然后说我们推出了一个新的叫XLOOKUP [01:02:43] extended lookup [01:02:45] 然后可以更方便地查找 [01:02:48] 查找 [01:02:49] 就这个Excel是在演演化的 [01:02:52] 那么马上这个需求就会变得模糊不清 [01:02:57] 我到底是应该兼容一个旧版本的标准 [01:02:59] 使得我在各种人身上都能打开 [01:03:03] 我发给那个教务老师 [01:03:05] 教务老师也能打开这个二维码 [01:03:07] 还是我要做一个 [01:03:08] 就是我自己本我自己用一用即抛的 [01:03:10] 那我就可以用最新的语法实现最简单的公式 [01:03:15] 那到底是哪个 [01:03:16] 然后你看到的是 [01:03:19] 如果需求变了 [01:03:21] 你的代码维护是极度痛苦的 [01:03:24] 你想如果AI给你vibe code了一个这个用2023 [01:03:30] Excel2023公式的这个这个代码好 [01:03:33] 你突然说哎呀不行 [01:03:35] 2023不行 [01:03:35] 要改成22019 [01:03:37] 要改成2003好 [01:03:39] 全完蛋 [01:03:41] 就是就是你这是摧毁性的 [01:03:42] 你的设计和实现就会从头回到原点 [01:03:46] 然后重新再做一遍 [01:03:47] 那对于一个2000行的项目来说 [01:03:49] 这个是OK的 [01:03:50] 你可以重做 [01:03:51] 但是如果这个2000万的项目 [01:03:53] 已经嵌入到了一个巨大无比的需求 [01:03:55] 嵌入到教务系统的时候 [01:03:56] 它和其他的部分产生关联的时候 [01:04:00] 加钱 [01:04:02] 那你作为这个项目经理说话 [01:04:04] 我要加人 [01:04:04] 我要加钱 [01:04:05] 然后这样子你这是现实的 [01:04:09] 对我没办法 [01:04:10] 我只能用这个方式来满足你的需求 [01:04:13] 但这显然就是一个架构没有设计好的 [01:04:16] 这个还有很多 [01:04:17] 比如说我的需求 [01:04:19] 可能等我最后出来以后 [01:04:22] 我对它放的位置不满意 [01:04:24] 我要把它整体往右移一格 [01:04:26] 如果你所有的单元格都是这个硬编码的 [01:04:30] 就直接立即崩溃 [01:04:32] 你看到他对需求的变更抵抗是很糟糕的 [01:04:37] 所以如果我们跳出问题看问题 [01:04:41] 其实这是一个编译的问题 [01:04:45] 我要开个A [01:04:46] 正常是这样的 [01:04:49] 先把这个关掉嘶 [01:04:53] 额我可以给我一个从字符串到二维码的伪代码 [01:05:06] 这个问题本身是清楚的 [01:05:14] 他显然这个or react react [01:05:16] 就是我给他了一个非常明确的问题 [01:05:20] 他还要去读文件 [01:05:23] 二维码是完全有规范的好 [01:05:27] 他给了一个这个你看非常短 [01:05:34] 非常难 [01:05:34] 而且这个就是一个代码 [01:05:37] 他说有什么版本纠错等级掩码 [01:05:40] 然后他有一个大小 [01:05:41] 然后他有数据编码 [01:05:43] 他现在有0100 [01:05:44] 然后有一个八位组(byte) [01:05:45] 怎么然后有一个纠错码 [01:05:48] 一个纠错码 [01:05:49] 它是一个 data 乘以 X 的 26 次方模(一个多项式) [01:05:52] 然后134个码字 [01:05:55] 最后加上一个ECC [01:05:56] 最后直接放到这个空间里 [01:05:58] 那为什么这么简单的东西 [01:06:02] 在Excel里面实现这么困难 [01:06:05] 这个gap到底是在哪里 [01:06:07] 如果你跳到问题外面 [01:06:09] 你就很明显能看到二维码 [01:06:12] 容易用算法来描述 [01:06:14] 就这个过程 [01:06:15] 这个过程我用一个点PY [01:06:22] Q2点PY [01:06:24] 那就再容易不过了 [01:06:25] 这就是啊明年保研机试我就出一个这个题 [01:06:29] 让你让你去实现 [01:06:31] 最后打一个这个00101矩阵 [01:06:34] 好像是一个OK的来测试大家是不是有一定的 [01:06:37] 如果要拖拖网的话 [01:06:39] 还必须要拖网的话 [01:06:40] 是一个不错的测试大家的题目 [01:06:42] 但是我们的要求是Excel [01:06:49] 我们不存在任何一个翻译器 [01:06:52] 一个编译器可以把一个Python编译成Excel [01:06:57] 这显然也是做不到的 [01:06:58] 就Excel它本质上是为了这个财务 [01:07:01] 报表啊这些而设计的 [01:07:04] 管理学生的成绩是一个小的数据库 [01:07:06] 它本身就不是为了做Turing complete计算来做的 [01:07:11] 所以没有一个这个p y to Excel的编译器 [01:07:15] 叫py2xl这个东西不存在好 [01:07:21] 然后如果我们直接把它变成 [01:07:22] 比如说伪代码或者是自然语言 [01:07:24] 然后我们用LLM 去编译 [01:07:27] 那我们也看到了这个LM的这个问题 [01:07:32] 他就直接上手就做 [01:07:34] 做了就啊需求漂移的时候 [01:07:37] 就会遇到很大的问题 [01:07:39] 所以我们你马上就知道 [01:07:41] 如果我有个好的架构 [01:07:43] 我是不是可以在Python和Excel中间构造另外一个语言 [01:07:51] 然后这个语言它既可以很容易的翻译成Excel [01:07:57] 又可以继承一定的Python的功能 [01:08:02] 就是刚才那个伪代码 [01:08:03] 我希望我要的东西其实是一个中间的编程语言 [01:08:06] 然后这个编程语言能够支持描述这些东西 [01:08:11] 描述这些这些这些运算中需要的东西 [01:08:14] 然后他们又语言又可以翻译成 [01:08:17] 我做了这样的一个设计 [01:08:22] 我这样的语言是可以翻译成Python的 [01:08:25] 就比如说 [01:08:26] 你们都写过那种表达式求值的程序 [01:08:30] 都写过表达式求值的程序 [01:08:32] 我就是做一个表达式树 [01:08:34] 比如说我就说比如说异或加什么AB [01:08:41] 然后或者是x of1 [01:08:44] 这可以取出一个 [01:08:46] 取出一个字符串里面的某一个 [01:08:50] 这就是一个小的编程语言 [01:08:52] 然后这个编程语言显然很容易翻译成Python [01:08:56] 你直接把它打包就行了 [01:08:58] 它递归的翻译 [01:08:59] 你把一个数 [01:09:00] 你把任何一个表达式树dump出来 [01:09:02] 你就是递归的左左子树右子树 [01:09:04] 然后把它根据这个节点拼起来 [01:09:07] 所以这个语言可以翻译成Python [01:09:09] 这个语言也可以翻译成Excel [01:09:11] 我只要求每一个节点都有一个 [01:09:15] 翻译成Excel的规则 [01:09:18] 我可以把这个东西放到一个格子里 [01:09:20] 任意的格子都可以 [01:09:22] 然后把这个结果放到一个格子里 [01:09:24] 然后我就可以在这里做出一个 [01:09:26] 这两个格子拼起来的结果 [01:09:28] 好那我就好办了 [01:09:30] 我让大语言模型生成这个语言的二维码 [01:09:37] 计算的逻辑 [01:09:38] 然后我用把这个语言翻译成Python去验证 [01:09:42] 看我的这个公式写的 [01:09:44] 然后这个公式写的 [01:09:46] 等我公式写对了 [01:09:47] 我再去写Excel [01:09:48] 然后你看到这样的架构 [01:09:51] 不就是我们刚才在第一个那个最小的例子里 [01:09:54] 我讲那个get o p t [01:09:55] 你说我们要做要写一个程序 [01:09:58] 写一个命令行的工具 [01:09:59] 我们第一件事情先getopt得到一个parsing state [01:10:03] 再从那个parsing state出发去做下一步一样的设计 [01:10:11] 所以你们你们作为一个unsupervised learner [01:10:13] 你们作为神经网络 [01:10:14] 大家神经网络你就是看好的东西 [01:10:17] 你看的多了 [01:10:18] 你自然就能泛化了 [01:10:19] 当然你看的多的时候 [01:10:20] 你要有意识去 [01:10:21] 意识去看好这个东西 [01:10:22] 为什么要这样 [01:10:24] 你回头突然有一天你会想通对 [01:10:26] 看到某一个blog的时候 [01:10:27] 或者有一天听我上课的时候 [01:10:29] 突然就想通了一件事 [01:10:30] 你就知道原来这个世界上做的 [01:10:33] 大家做的事情都是都是差不多的 [01:10:35] 就是在做在做这样的这样的事情 [01:10:39] 一份中介语言 [01:10:40] 两种执行方式 [01:10:41] 所以我就可以做构造一次 [01:10:43] 我的架构就是我做了这样的一个架构 [01:10:47] 这个架构是从LLM翻译成中间语言 [01:10:52] 这个用来验证这个用来交付好一个 [01:10:57] 就是一个这样的架构 [01:11:02] 啊这些AI给我生成的slop也不重要了 [01:11:05] 而且这个架构在这个架构上我还可以发展出 [01:11:09] 比如说优化什么是优化 [01:11:12] 我不是一个语言吗 [01:11:14] 我一个语言我如果觉得性能不好 [01:11:17] 哪个地方生成了太长的代码 [01:11:18] 我就有一些改写的规则 [01:11:21] 我可以把一个蛋 [01:11:22] 一个程序从一个形态改写成另一个形态 [01:11:25] 比如说你们都知道 [01:11:26] 如果我有一个变量在循环里面每次都计算 [01:11:29] 但它们的值都一样 [01:11:30] 我就可以把这个变量的计算提到循环的外面 [01:11:33] 编译器可以自动做这件事 [01:11:35] 语言模型 [01:11:36] 就你自己也可以手动把这个计算给移出去 [01:11:39] 你可以做手工的优化 [01:11:40] 而如果我有个中间语言 [01:11:42] 我就可以让大语言模型去优化它 [01:11:45] 然后你在这个计算机系统里面学到的 [01:11:48] 比如说profiling guided optimization [01:11:50] 你可以先要去知道性能瓶颈在哪里 [01:11:52] 然后再用这个profile [01:11:53] 性能的profile再去指导你代码应该写成样更好诶 [01:11:57] 你的概念又用上了 [01:11:58] 所以一个好的架构 [01:12:00] 可以承载这个需求的变化 [01:12:03] 未来Excel如果Excel的版本要Excel版本改了 [01:12:07] 没问题 [01:12:08] 我无非就是把一些算子 [01:12:11] 用另外一个方式去实现 [01:12:13] 偶然后讲了算子的时候 [01:12:15] 你发现今天这个AI inference也在干这个事 [01:12:19] 类似的是人类人类就是就是这样 [01:12:22] 你会当然他们因为他们有很多领域的知识 [01:12:24] 所以在他们领域里面有非常多好的啊设计 [01:12:27] 这些都是啊值得非常值得大家大家学习的好 [01:12:33] 那么终于到我们最后一个 [01:12:36] 十行的讲完了2000行的 [01:12:38] 讲完了50万行的架构应该应该怎么设计 [01:12:42] 当然我觉得架构就一就一件事 [01:12:45] 跳出问题看问题 [01:12:47] 因为当我们就当我们谈架构师的时候 [01:12:50] 你想到了什么 [01:12:51] 想到那些架构师都是在那框图数据库 [01:12:55] 微服务 [01:12:56] 流式数据处理应用框架 [01:12:58] 然后选这个应用框架 [01:12:59] 这几个应用框架搭起来 [01:13:00] 我就解决这个叫教务系统的问题了 [01:13:06] 在我们开始看架构之前 [01:13:08] 我们先来看一下 [01:13:10] 这个AI实现了一个什么样的架构 [01:13:15] 很好玩 [01:13:16] 这是我在它生成中间的时候看到的结果 [01:13:20] 所以我就当场我就发了一个小红书 [01:13:22] 说这个啊GPT-6说这个我不当人 [01:13:27] 我不做人了 [01:13:27] 就是这一看刚才那个汉诺塔 [01:13:29] 就是我在数学上我能我能想清楚 [01:13:32] 但数学上就是对的 [01:13:33] 那我那我不就是对的吗 [01:13:34] 这毫无 [01:13:37] 毫毫无破绽 [01:13:38] 然后然后他写的时候 [01:13:39] 他一个function [01:13:41] 你看他叫function applications括号 [01:13:43] 然后啪啪啪啪啪啪啪 [01:13:45] 然后后来后来我发现GPT还很贴心的 [01:13:48] 用那个代码的这个格式化工具 [01:13:51] 帮我把它格式化完了 [01:13:52] 就你如果现在看这个 [01:13:59] 现在看 [01:14:02] 教务系统的 demo 的话 [01:14:05] 他还是一个monolithic [01:14:07] 这还 [01:14:10] 一个54 [01:14:13] 一个83KB的TSX [01:14:15] 一个38KB的这个这个services [01:14:20] 还是很有水准的 [01:14:23] 就是他格式化了 [01:14:24] 格式化以后 [01:14:25] 你看起来这个这个东西就正常一些了 [01:14:27] 虽然我真的觉得他还蛮厉害的 [01:14:33] 就他真的很真真的真的真的真的蛮厉害的 [01:14:36] 这个东西能能能能做对我 [01:14:39] 我们也可以问他 [01:14:44] 啊这个教务系统使用了怎样的架构 [01:14:57] 我们让来让D老师来评价一下 [01:15:00] GPT生成的东西 [01:15:03] 然后当时我就我就惊了 [01:15:06] 惊了以后我就启动了一个独立的session [01:15:08] 然后问那个就那个那个那个GPR在跑 [01:15:12] 问说这个软件能维护吗 [01:15:15] 害怕的问他说能能能 [01:15:17] 现在只不过是把这个业务逻辑 [01:15:19] 挤在几个大文件里 [01:15:20] 但是这个东西维护是完全没有没有问题的 [01:15:23] 所以我所以我越来越觉得这个这个好 [01:15:29] 他说你看经典三层单体前后端前后端分离 [01:15:36] 而且有几个生产级横切设计 [01:15:42] 乐观锁idempotent [01:15:44] 这是我 [01:15:47] 我肯定会在某一次课的时候来讲讲这些 [01:15:50] 事务审计统一统统一错误 [01:15:52] 所以所以你看就是他觉得很有很有自信 [01:15:55] 他确实做了工程上 [01:15:56] 就是那个你们做不来做不来的 [01:15:59] 这个至少要摸爬滚打几年才理解的这个设设计 [01:16:02] 它有这个软件工程的直觉 [01:16:05] 然后你看它有数据驱动 [01:16:08] 横向扩展 [01:16:08] 扩展性很好 [01:16:10] 当然也有也有也有差差的一面 [01:16:14] OK啊挺好玩的 [01:16:18] 这个很有趣 [01:16:19] 就很快AI生成生产软件的方式就要超过 [01:16:22] 超过人类了 [01:16:24] 但是我觉得就是如果我跳出问题看问题 [01:16:28] 我觉得这还不是一个这个非常好的架构 [01:16:32] 因为本质上这是一个CRUD的架构 [01:16:37] 就是增删改查的架构 [01:16:39] 然后这样的一个架构 [01:16:41] 来自于一个什么样的 [01:16:45] 就是基本的原理 [01:16:47] 基本原理是 [01:16:49] 我们的软件是物理世界过程 [01:16:53] 在信息世界里面的投影 [01:16:55] 然后我们的物理世界是有状态的 [01:16:57] Everything is a state machine [01:16:58] 我自己也是一个state machine [01:17:00] 所以软件系统它很容易会建模成一个state [01:17:05] Transition system [01:17:06] 也就是说它有一个状态 [01:17:10] 然后这个状态一般来说是用数据库 [01:17:12] 就persistent的状态是用数据库 [01:17:13] 比如说我可以用一张表来表示学生 [01:17:18] 学生有学号 [01:17:19] 姓名 [01:17:20] 院系等 [01:17:21] 然后我还有一张表表示学院表示选课表示 [01:17:24] 反正各种各样的东西都可以用二维的表来表示 [01:17:28] 这是你们在数据库课上面学过的 [01:17:31] 然后数据库要求你消除这个表里面所有的冗余 [01:17:34] 要把这个数据库factor成normal form [01:17:36] 什么意思 [01:17:37] 你最好不要学生里面有一个课程 [01:17:42] 比如说学生有一个学号 [01:17:44] 学号 [01:17:45] 学号是唯一的 [01:17:46] 比如说1234好 [01:17:47] 那学生学校学生有个姓名 [01:17:51] 那你在另外一张表里面 [01:17:53] 如果要引用一个学生 [01:17:54] 尽量不要用姓名来引用 [01:17:57] 因为姓名学生可能会重名 [01:17:59] 然后就算不重名 [01:18:00] 这个也会带来一些麻烦 [01:18:02] 你更好的是不要用姓名 [01:18:05] 就把这一列从这个表里面去掉 [01:18:07] 而是这里面放一个学生的id [01:18:08] 比如说1234 [01:18:09] 这个id就相当于一个链接 [01:18:11] 这个链接指向了学生的这个信息 [01:18:13] 然后这个学生还可以再指向别的表 [01:18:15] 里面的某一些信息 [01:18:16] 这样把信息给最小原则就不会 [01:18:20] 其实是铲除冗余的dependency [01:18:24] 我一个地方改了以后 [01:18:25] 我不会需要改另外一个地方 [01:18:26] 因为如果这个学生真的改名字了 [01:18:28] 不要笑这件事情在中国是合法的 [01:18:31] 你现在你在这个表上 [01:18:32] 如果你用这个方式来 [01:18:34] 如果用分解的方式来 [01:18:35] 那你学生改了名字 [01:18:36] 这个名字改掉就行了 [01:18:37] 你就记一个曾用名 [01:18:38] 但如果你是这样的两份就存了两份名字 [01:18:42] 那对不起 [01:18:43] 你要知道这两个地方都要都要修改 [01:18:47] 这就是一个很大的很大的代价 [01:18:51] 然后我们一般来说会把这样的世界建模成 [01:18:56] 这个状态 [01:18:58] 然后在任何时候这个状态就代表了 [01:19:00] 比如说在现在这个时间点上 [01:19:02] 南京大学有哪些学生 [01:19:04] 然后有哪些学生选了哪些课 [01:19:07] 有哪些老师等 [01:19:08] 然后每当我的信息系统就前后端分离 [01:19:11] 这也是一个非常经典的 [01:19:12] 刚才说的前后端分离的架构 [01:19:13] 那如果比如说今天我选择退学 [01:19:17] 我没有买到token [01:19:18] 然后我选择退学 [01:19:20] 然后退学 [01:19:20] 那就是一个啊update的这个数据库的过程 [01:19:25] 然后他就从一个状态迁移到了下一个状态 [01:19:28] 然后这个状态那这里就状态 [01:19:30] 比如说我现在A现在还在 [01:19:32] 那在下一个状态里面 [01:19:33] A就没有了 [01:19:34] A这个记录就不存在了 [01:19:35] 那这就是一个始终是consistent的视图 [01:19:38] 你永远是你 [01:19:39] 永远我在教务系统里面打开那个网页 [01:19:41] 就看到这个东西的一个投影 [01:19:44] 它的投影这是非常经典的架构 [01:19:46] 没有任何的错误 [01:19:49] 但是但是但是但是如果我们 [01:19:56] 想教务系统为什么不适合 [01:20:01] 这样的一个CRUD的架构 [01:20:05] 就是因为当前的状态里面 [01:20:08] 就是当我看这个当前状态的时候 [01:20:11] 我看到的是一片祥和的景象 [01:20:13] 但是这个当前状态所有的历史它丢掉了 [01:20:18] 比如说如果我真的在某一个地方里面 [01:20:21] 学生的姓名存了两次 [01:20:23] 这是非常正常的 [01:20:26] 在在某一个表里面姓名学了两次 [01:20:28] 因为这可能是一个字符串 [01:20:31] 这可能是一个字符串 [01:20:33] 然后这个字符串就是什么加当时加学号 [01:20:37] 授予授予谁谁谁什么什么的 [01:20:39] 一个这个这个东西对他没有用关联的方式 [01:20:43] 而用一个字符串加他写到数据库里了 [01:20:45] 那这个时候数据库内部就发生了啊不一致了 [01:20:50] 所以所以比如说GP给了我一个例子 [01:20:53] 他说学生学生在学生字段里面 [01:21:01] 学在这个学生表里面 [01:21:03] 比如说学生那有student的id [01:21:06] 然后比如说有一个有一个column [01:21:09] 他就叫是否允许毕业 [01:21:15] 那比如说你们你们现在比如说学分 [01:21:18] 你再上我的课 [01:21:19] 应该学分还不够 [01:21:19] 所以应该都不允许毕业 [01:21:22] 然后有的有的同学这个学分修够了 [01:21:25] 他就允允允许毕业 [01:21:27] 而这个允许毕业的 [01:21:30] 这个一是有很多其他的状态算出来的 [01:21:33] 是因为之前之前有触发 [01:21:35] 比如说每当你修了一个学分 [01:21:39] 或者你的毕业设计状态改变等的时候 [01:21:42] 他都要重新计算一个 [01:21:43] 你的这个毕业是否毕业的逻辑 [01:21:46] 这样因为这个这个东西对信息系统来说很重要 [01:21:48] 这个教务员老师需要能够对 [01:21:51] 辅导员老师能够在教务系统里面导出 [01:21:53] 现在还到现在还毕不了业的学生 [01:21:55] 要找你一个一个谈话 [01:21:57] 他必须要在教务系统里才能 [01:21:59] 这是个很典型的需求点才能才能才能做到 [01:22:03] 但是这样的东西是历史上面的计算的 [01:22:08] Dependency [01:22:08] 然后他们随着当前的状态都没有了 [01:22:11] 你知道这个学生可以毕业 [01:22:12] 但是你不知道他为什么可以毕业 [01:22:14] 你知道他这个不能毕业 [01:22:17] 你不知道他为什么不能毕业 [01:22:19] 这个计算的过程过程丢掉了 [01:22:21] 而计算的过程丢掉 [01:22:23] 这个软件又是现实世界物理过程的一个有损的 [01:22:28] 投影 [01:22:29] 那么最后导致的是物理世界里面我建模不了的 [01:22:36] 全部都需要通过其他的途径来解决 [01:22:40] 今天政策更新了 [01:22:43] 领导批准了 [01:22:44] 但是这个培养计划新的方案 [01:22:47] 新的数据还没有导入到教务系统 [01:22:50] 你们经常遇到这个问题 [01:22:51] 不一致 [01:22:52] 就物理事件已经发生了 [01:22:53] 这个培养计划已经已经通过了 [01:22:55] 学期已经开始了 [01:22:56] 我已经上课了 [01:22:57] 这个没有灯 [01:22:58] 就物理世界已经发生的事实 [01:23:00] 在系统里面没有建模 [01:23:02] 然后在系统里没有建模的时候 [01:23:04] 哎呀比如说这个教室还不能用 [01:23:06] 就第一周很典型的第一周 [01:23:08] 整个这个仙林这一片都还在修 [01:23:10] 教室不能用 [01:23:11] 然后不能用的时候 [01:23:12] 这个显然教务系统没有建模 [01:23:16] 这一点没有建模 [01:23:17] 这一点最后就靠他们手改重新安排教室 [01:23:21] 然后再把那个教室安排到另一个这个 [01:23:26] 我第一周就去另外一个教室教室上课了 [01:23:28] 然后这样慢慢慢慢随着时间的积累 [01:23:30] 你的系统里面会有大量不一致的数据 [01:23:33] 随着时间变成无法解释的黑洞 [01:23:36] 哎明明这个同学不该不能毕业的 [01:23:39] 他为什么毕业了 [01:23:42] 突然想起回想起来的时候 [01:23:43] 明明这个同学没有交学费 [01:23:45] 应该毕不了业的 [01:23:47] 为什么 [01:23:48] 为什么他能毕业 [01:23:49] 系统里面就无数无数 [01:23:51] 这样你想都不能想的不一致好 [01:23:56] 所以你们在面对这种需求的时候 [01:24:00] CRUD就会死 [01:24:01] 比如说南大没系了 [01:24:05] 当前环境异常 [01:24:09] 啊我知道他对LINUX很害怕 [01:24:11] LINUX的这个 [01:24:15] 南大没系了 [01:24:17] 最我们是最后一批系 [01:24:19] 然后现在成为了计算机学院 [01:24:26] 在南大院系发生调整的时候 [01:24:29] 这个教务系统就瑟瑟发抖了 [01:24:31] 因为还是还是这样的CRUD它是一个这个 [01:24:38] Point of truth [01:24:39] 就所有的事实都在这 [01:24:40] 那好 [01:24:42] 那你我们的这个department变了 [01:24:46] 那我应该是把旧的这个department [01:24:48] 就我这个世界里面实际上是一个对象 [01:24:50] 虽然是表 [01:24:51] 它还是对象 [01:24:52] 这个表就叫计算机 [01:24:55] 那我要把他的name从计算机系改成计算机学院吗 [01:25:01] 要妈 [01:25:04] 要也不对不要也不对 [01:25:07] 你想首先如果要这个 [01:25:11] 就你把这个这个object改了 [01:25:13] 计算机系现在确实是变成计算机学院了 [01:25:15] 但是这意味着我的毕业的记录 [01:25:20] 如果我select我到系统里面查询 [01:25:22] 我就变成了这个 [01:25:25] 2011年毕业于南京大学计算机学院 [01:25:29] 这个就属于我事实不符了 [01:25:30] 因为我在2011年的时候时候 [01:25:33] 这个 [01:25:34] 这个前面计算机学院它生成了一个对 [01:25:37] 系统里面给我生成打印什么证明的时候 [01:25:40] 给我生成了这个东西 [01:25:41] 好糟糕 [01:25:42] 然后那如果不变 [01:25:46] 也很麻烦 [01:25:47] 因为你的你现在所有的这个 [01:25:51] 这个department id都指向这个学生的 [01:25:53] 这个department id都指向计算机系 [01:25:55] 还是得把它改过来 [01:25:57] 你需要对它做一个数据库的迁移 [01:25:59] 你要创建一个新的新的对象 [01:26:01] 然后新的对象和旧的对象之间的这个关联关系 [01:26:05] 你还要把它考虑清楚 [01:26:08] 所以所以你看到 [01:26:11] 如果你假设这个世界就是这样的 [01:26:13] 这个投影不变了 [01:26:14] CRUD当然是一个完美的架构 [01:26:17] 但是只要你假设这个需求会发生摧毁性的变化 [01:26:22] 那么这个数据库你每次你就要这样想 [01:26:26] 你每次都会涉及一个数据库的迁移 [01:26:30] 业务逻辑的重写 [01:26:32] 你代码是可以重写 [01:26:33] 但是你的历史因为历史消失了吗 [01:26:36] 我们说这个因为这是一个data base的transaction [01:26:40] 对他update完了以后 [01:26:41] 就从这个状态彻底的挪到了新的状态 [01:26:46] 你做一次数据库的迁移 [01:26:49] 你就要考虑历史上所有可能发生的情况 [01:26:51] 而且何况这个数据库里面 [01:26:53] 已经有很多的不一致了 [01:26:55] 你很难写出一个规则来迁移 [01:27:00] 你还要冒着数据库迁移完了以后 [01:27:03] 这个数据库就坏掉了 [01:27:05] 你就像我那个把选课给改成一一样 [01:27:08] 因为因为你在你的测试环境里面做一次迁移 [01:27:11] 和你把一个真的这个 [01:27:13] 学校里面所有的数据都做一次迁移 [01:27:15] 这个难度是完全不一样的 [01:27:18] 所以你们就理解了 [01:27:19] 为什么现在就或者说到今天之前 [01:27:23] 软件的设计实现ERP这类的系统 [01:27:25] 不管是教务系统 [01:27:27] 它的设计实现维护都是手艺活 [01:27:30] 就是我们就你看我调侃 [01:27:31] 这个教务系统做的做的做的不好 [01:27:35] 它确实就确实有做得好的这一面 [01:27:38] 但是就是说或者不好意思说错了 [01:27:41] 就他确实有做的不好的一面 [01:27:43] 但是你反过头来想 [01:27:45] 他作为一个软件工程的实践者 [01:27:48] 他可能在这公司在2000年的时候成立了 [01:27:51] 然后在2000年成立的时候 [01:27:52] 他做的唯一的选择就是为 [01:27:55] 就参参考一个已有的企业架构来做一个教务 [01:27:59] 教务系统 [01:28:00] 来抢占这个教育市场的第一桶金 [01:28:02] 然后他把这个东西做出来了 [01:28:04] 然后做出来以后 [01:28:04] 他就是一个CRUD的系统 [01:28:06] 那他一旦背上这个技术债 [01:28:09] 他就要在这个中国大学教育改革 [01:28:13] 这个几十年的时间里面 [01:28:14] 不断的扛下所有 [01:28:16] 所以这是一门这是一门手艺手手艺活 [01:28:19] 这里面还有一些有意思的有意思的故事 [01:28:21] 这个我读PHD的时候 [01:28:26] 我们的软工软件工程组 [01:28:29] 就我们我是软件所的 [01:28:30] 然后我们软隔壁的软件工程组 [01:28:33] 接了学校研究生院的教务系统的这个订单 [01:28:38] 那可能就二三十万吧 [01:28:39] 可能可能这是一个 [01:28:42] 在大家来看可能是一个比较大的订单 [01:28:44] 实际上在软件这种定制化软件里面 [01:28:46] 是比较小的订单 [01:28:47] 因为你要不停的维护这个事 [01:28:49] 维护这个屎山 [01:28:50] 但后来这个故事是这个屎山维护不下去了 [01:28:52] 我还记得我们当当时刚读PHD [01:28:57] 那是我的室友 [01:28:58] 然后我就经常跟他抬杠 [01:29:00] 然后我就跟就说你就我会跟他聊这个 [01:29:03] 研究生院这个系统是怎么 [01:29:05] 那个时候对叫研究生院 [01:29:07] 研究生院的这个系统怎么设计 [01:29:09] 然后我就经常经常喷他 [01:29:11] 因为他是一个这个非常典型的CRUD架构 [01:29:14] 我说你最后会 [01:29:16] 会面临各种各样的问题 [01:29:19] 果然就在那几年 [01:29:21] 我们的研究生发生了巨大的扩招 [01:29:23] 然后很多管理 [01:29:24] 然后又多了很多研究生的类型 [01:29:27] 培养的类型 [01:29:29] 然后而且研究生又有这大家知道压力很大 [01:29:33] 然后经常会有这种调整 [01:29:35] 这都是他们在做需求设计的时候始料未及的 [01:29:38] 然后最后那个系统就彻底变成屎山 [01:29:40] 然后最后进入了就反正就不存在了 [01:29:43] 就现在统一变成公司维护的这个这个系统 [01:29:47] 他们还是有一些就是有一些领域知识 [01:29:49] 知道这些系统应该怎么把屎山给扶起来 [01:29:53] 但是但是如果我要重做一个教务系统的架构 [01:30:00] 我会怎么做 [01:30:03] 我就会看到这个single point of truth [01:30:07] 太危险了 [01:30:09] 它一旦你有任何的不一致 [01:30:11] 你都不应不应不知道应该怎么样 [01:30:13] 把它把它拿出来 [01:30:18] 然后你就想到数据结构 [01:30:21] 课上讲过一种非常神奇的东西 [01:30:23] 叫persistent data structure [01:30:25] 它可以得到这个数据结构 [01:30:27] 所有的历史 [01:30:28] 也就是你存一个数据结构 [01:30:30] 你存一个一棵树好 [01:30:35] 那这就是CRUD哎 [01:30:37] 你看数据库也是一个数据结构吗 [01:30:39] 所以你当前就是存一个库 [01:30:42] 然后你对它做一个操作 [01:30:44] 它就到下一个状态 [01:30:45] 这就是典经典的CRUD [01:30:47] 但我还有另外一种 [01:30:50] 我可以一开始只有一个节点 [01:30:54] 然后有一个节点以后诶 [01:30:57] 我可以创建 [01:30:58] 就下一次它数不变了 [01:31:00] 那我就可以这个节点还在 [01:31:02] 我把这个节点放过来 [01:31:03] 然后如果我到有一天 [01:31:05] 比如说这个数变了他 [01:31:12] 比如说我的这个要在这个节点上上上 [01:31:15] 新建一个 [01:31:16] 那我就把它从到根的路径上面这个长出来一个 [01:31:20] 然后它的左子树可以指向它 [01:31:22] 它的左子树可以指向它 [01:31:23] 这个数无论多大 [01:31:25] 我其实都是可以存的下来的 [01:31:26] 然后当然我可以这是这个 [01:31:30] 我可以存 [01:31:32] 就是我可以存状态 [01:31:36] 也可以存操作 [01:31:39] 也可以存操作 [01:31:40] 对状态的德尔塔这三种视图是等价的 [01:31:47] 你 [01:31:49] 你看我存它的日志 [01:31:50] 就是一个 write-ahead 的 journal [01:31:53] 你们在数据数据库操作系统课上都学过 [01:31:56] 然后你存这个他们的这个德尔塔 [01:32:01] 存 [01:32:02] 他们德尔塔就是git、SVN [01:32:04] 这样的版本管理里面的思思想 [01:32:06] 然后你存这个状态 [01:32:10] 当前的状态就是CRUD [01:32:12] 但是他们都可以帮你回放历史 [01:32:15] 得到当前的状态 [01:32:16] 所以如果你想要设计一个更好的架构 [01:32:25] 最好是先把干的所有的事情都先记下来 [01:32:29] 今天张三退学了 [01:32:30] 今天张三又复学了 [01:32:33] 你可以假假想 [01:32:35] 就比如说啊这个世界是 [01:32:37] 之前没有复学这个操作 [01:32:39] 没有复学这个操作 [01:32:43] 就是把张三从这个数据库里面抹掉好 [01:32:46] 抹掉了它就抹掉了 [01:32:49] 他可能有一些记录就删掉了 [01:32:51] 你要把这些这些和他 [01:32:53] 他和这个其他部分的关联重新给建起来 [01:32:56] 那你就要付很大的代价 [01:32:58] 你要加上复学这个这个操作 [01:33:00] 他又买到token了 [01:33:01] 然而如果你是用比如说记录所有的操作的方式 [01:33:07] 你就可以知道 [01:33:09] 通过replay可以知道张三复学前的状态是什么 [01:33:13] 然后你再把这个状态拿过来 [01:33:15] 他没有丢 [01:33:16] 就你就你的逻辑是不能减少的 [01:33:18] 就是你即即便使用记录日志 [01:33:21] 记录历史的方式 [01:33:22] 你比如说复学这个事情之前没有考虑过 [01:33:26] 我依然是要为他写一个逻辑 [01:33:28] 但是因为事实是不可篡改的 [01:33:31] 你世界里面所有东西都是对事实的投影 [01:33:34] 事实投影出来的 [01:33:37] 那你好像就有底气了一些 [01:33:39] 你就不怕错了 [01:33:41] 你就不会像刚才那样说 [01:33:43] 我要做一个数据库迁移的时候 [01:33:44] 我手在抖 [01:33:45] 因为你总知道你任何一个这个做错了 [01:33:50] 你都可以很快的回到退回到过去的一个快照 [01:33:53] 然后啊我把这个逻辑怎么做修复 [01:33:57] 再来然后你也可以看到 [01:33:59] 计算机系 [01:34:00] 计算机学院的问题相对来说也好解 [01:34:03] 解决一些了 [01:34:04] 我可以在某一个时刻发生了一个事件 [01:34:07] 叫这个院系调整 [01:34:12] 调整 [01:34:13] 然后你查询一个过去的人的时候 [01:34:15] 你会查询到他过去的版本 [01:34:17] 然后并且你知道他经历了这个 [01:34:20] 就说他经如果你要查询他过去 [01:34:23] 比如说毕业时候发生的事情 [01:34:24] 你直接查他毕业时候就可以 [01:34:26] 然后如果你想要得到一个它比较新的信息 [01:34:29] 你可以看到比如说投影出和这个人相关的 [01:34:33] 这个同学这个同学在比如说2007年入学 [01:34:38] 2011年毕业 [01:34:39] 然后到这个都是计算机系 [01:34:43] 然后计算机系在202 14年 [01:34:47] 改为了计算机学院 [01:34:48] 你看到我可以裁出一个 [01:34:50] 就总是你总是可以想要得到一个事实 [01:34:53] 当前的事实 [01:34:53] 你可以裁出一个和事实相关的历史 [01:34:57] 然后把这个相关历史replay得到 [01:34:59] 我现在要的状态 [01:35:00] 我可以查询过去 [01:35:01] 也可以查询 [01:35:01] 现在这就是一个好的架构设计 [01:35:03] 然后你祝贺你 [01:35:04] 你就发明了一个叫event sourcing的一个 [01:35:08] 非常经典的设计模式 [01:35:11] 它可以从这想法很简单 [01:35:13] 从历史事件生成生成状态 [01:35:16] 而不是直接维护这个CRUD的啊状态 [01:35:20] 你们可以回去可以在这个agent的带领下 [01:35:23] 去了解一下 [01:35:24] 比如说如果用 [01:35:32] 然后一般switch他有 [01:35:33] 就你马上会反应过来 [01:35:35] 是不是会有性能问题 [01:35:37] 因为要比大量的replay [01:35:40] 但这都不是很大的问题 [01:35:41] 我们总可以比如说用快照缓存 [01:35:43] 各种各样的这个 [01:35:55] 对他会有一些优化的优化的技巧 [01:36:00] 然后这是一个就是吹到 [01:36:02] 比如说你已经在屎山上了 [01:36:03] 你把它变成是一个event sourcing的这个设计 [01:36:07] 就会变得很困难 [01:36:08] 但是如果你从一开始就做event sourcing的设计 [01:36:12] 很多事情就问题都不会发生 [01:36:14] 你就能比较好的解决 [01:36:17] 然后这样就有了很多这种fancy的软件工程概念 [01:36:19] 什么CQRS [01:36:20] 这是command query [01:36:22] Responsibility [01:36:23] Segregation [01:36:23] 但这和这个event sourcing是独立的 [01:36:26] event sourcing是独立的 [01:36:27] 还有这种什么domain driven design这种 [01:36:31] 然后其实你们都不需要 [01:36:32] 就你们可以去看去看 [01:36:34] 但是你只要回到这个最初的first principle [01:36:37] 你要设计一个50万行教务系统的时候 [01:36:40] 我们跳出这个问题 [01:36:42] 看到教务系统根源的问题在哪里 [01:36:44] 你就能做一个正确的架构设计 [01:36:47] 哪怕你没有听说过这些 [01:36:48] 你也能做一个正确的架构设计