[00:00:01] 今天我们继续讲需求和架构的第二个部分 [00:00:05] 需求和架构其实有的时候是分不开的 [00:00:08] 因为我们从第几节课 [00:00:09] 我们就开始说 [00:00:11] 当我们要做一个软件或者做一个产品 [00:00:13] 它足够复杂的时候 [00:00:15] 你就会面对这个intention [00:00:16] specification和implementation的啊gap [00:00:19] 然后我们从最早的那个瀑布模型开始 [00:00:22] 你也看到我们是怎么样去理解这个 [00:00:25] 那gap上用数据依赖的方式 [00:00:28] 去做一个minimum viable product [00:00:31] 然后来控制这样的复杂性 [00:00:34] 那实际上上一节课我们也讲到 [00:00:39] 当我们谈论这个需求和架构的时候 [00:00:42] 有一个原则就是代码之外有客户的规律 [00:00:46] 客户之外有社会的规律 [00:00:47] 社会之外有宇宙的规律 [00:00:49] 所以你站在哪一个层面去看这个需求 [00:00:52] 你可能好 [00:00:53] 上一节课的时候 [00:00:54] 我带大家 [00:00:54] 大家可能还记得我去用浏览器操作的方式 [00:00:59] 逆向了整个教务系统的这个需求 [00:01:02] 那不能是整个教务系统 [00:01:03] 就是和教师相关的需求 [00:01:05] 然后得到了一份这个需求规格的说明书 [00:01:07] 然后那个说明书上写的很好 [00:01:09] 这就是这个是符合软件工程实践 [00:01:13] 也符合做产品的这样的一个做需求的方式 [00:01:17] 但是你作为整个系统的设计者 [00:01:21] 或者整个系统的架构师 [00:01:23] 你可能要站在这个需求之外来 [00:01:26] 去想哎 [00:01:27] 这些你当然是可以说 [00:01:29] 我现在就选一个架构 [00:01:30] 选一个数据库 [00:01:31] 然后就把这些需求都实现了 [00:01:32] 你可以交付一个百分之百满足客户需求的产品 [00:01:36] 但是这个产品很有可能会在未来的维护当中 [00:01:39] 陷入一个长期的泥潭 [00:01:40] 也就是说你在做任何设计的时候 [00:01:43] 做的那一刻起 [00:01:44] 你就背上了一定程度的技术债 [00:01:46] 然后这个技术债你会一直一直要背下去 [00:01:49] 而且啊大家如果留意编程 [00:01:52] 就是在编程的时候注意过很多细节的话 [00:01:55] 其实我们从十行的代码 [00:01:57] 最小的一份代码 [00:01:58] 今天其实我还有几个例子 [00:01:59] 最小的代码到你可能要做一个50万行代码 [00:02:02] 100万行代码 [00:02:03] 1000万行的代码 [00:02:04] 你要做一个大系统的时候 [00:02:07] 它其实这个架构都是无处不在的 [00:02:10] 而且上节课大家还记得有一个图说 [00:02:13] 我们从最初的时候什么也没有的时候 [00:02:15] 我们就需要做决定 [00:02:16] 然后一旦我们做决定 [00:02:17] 那它就会影响未来的走向 [00:02:19] 所以其实你的软件架构的设计 [00:02:23] 通常来讲都是有很大的改进空间的 [00:02:26] 所以你们如果做实验 [00:02:27] 我应该这两天会把下一个实验放上来 [00:02:30] 做第一个实验的时候 [00:02:31] 你可以直接 [00:02:32] 像我那次上课一样 [00:02:34] 直接把需求复制粘贴进去 [00:02:35] 让AI帮你直接做一个东西 [00:02:38] 然后你不停的跟他聊天修改 [00:02:41] 但是大家要记得 [00:02:44] 你如果马上第一反应说诶 [00:02:47] 这个东西可以这样做吗 [00:02:49] 可以这样设计数据库 [00:02:50] 你的第一反应应该是 [00:02:51] 也许我可以回过头来再多想一想 [00:02:54] 可能会有更好的方案 [00:02:57] 好这是上节课内容的一点回顾 [00:03:01] 然后呢我们谈软件工程的时候 [00:03:04] 这个brooks就说了 [00:03:05] 我们软件工程的本质就是驾驭复杂性 [00:03:09] 当我们在谈需求的时候 [00:03:10] 你看到那个需求规格说明书的时候 [00:03:12] 你就知道现实中的软件是非常复杂的 [00:03:17] 非常复杂 [00:03:19] 那有一些复杂性是没有办法消除的 [00:03:23] 这个problem domain里面的essential complexity [00:03:26] 他的额选课的问题就是这么复杂 [00:03:29] 我们要考虑这个课程的容量 [00:03:31] 先修的要求 [00:03:32] 时间的冲突 [00:03:33] 他要排一个 [00:03:34] 还要还要排课 [00:03:35] 还要考虑教室的这个情况 [00:03:37] 我们的系统里面还支持 [00:03:39] 比如说我们老师和同学都可以借教室 [00:03:41] 这个物理世界就是复杂的 [00:03:43] 而这个物理世界在信息世界里面的投影 [00:03:47] 它的这个复杂性是没有办法消除的 [00:03:49] 那没有办法消除的时候 [00:03:51] 我们其实是用架构软件的架构来更好的去 [00:03:55] 管理这样的复杂性 [00:03:57] 除此之外 [00:03:58] 我们刚才讲到技术债 [00:04:00] 你在做任何实现决定的时候 [00:04:03] 诶 [00:04:03] 如果我们要用这个数据库系统 [00:04:04] 或者你就用文件系统管理的时候 [00:04:06] 你都会引入一个trade off space [00:04:09] 你有时候你文件系统 [00:04:11] 你是管理起来是很方便 [00:04:13] 我想hack它也很容易 [00:04:14] 但是你带来的代价是 [00:04:16] 它的性能可能远远不如数据库 [00:04:17] 而且它有并发控制的问题 [00:04:19] 文件系统本身是不支持这种原子性的事物 [00:04:23] 就是oh nothing [00:04:24] 我要我可以读多个数据 [00:04:26] 写多个数据 [00:04:27] 然后并且这个整个读和写看起来是atomic的 [00:04:32] 就像我上了一把锁 [00:04:33] 然后stop the world一样 [00:04:35] 文件系统是没有这个保证的 [00:04:36] 虽然其实有比较疯狂的人 [00:04:39] 想要在操作系统里面实现这种文件 [00:04:41] 系统级的transaction [00:04:43] 数据库能够帮你实现transaction [00:04:46] 但是它带来的缺点就是你每次调试的时候 [00:04:49] 都需要经过一个中间层 [00:04:51] 你需要看数据库的时候 [00:04:53] 你不能像文件系统一样 [00:04:55] 使用所有的UNIX工具去组合它 [00:04:57] 理论上说也可以 [00:04:58] 你还是可以把query和其他的工具组合起来 [00:05:01] 那样就会比如说让A阵做 [00:05:03] 就会引入一个额外的转转转换层 [00:05:06] 然后这些都是你做任何技术的选型都会有的 [00:05:11] 这种accidental complexity [00:05:13] 这两个要分开 [00:05:16] 然后其实好的 [00:05:17] 就如果你们在长期的 [00:05:20] 学习各种各样好的软件架构的过程中 [00:05:22] 你们就会看到诶什么样的 [00:05:24] 什么时候应该用什么样的架构 [00:05:26] 来减少这个accidental [00:05:28] complexity以及怎么样 [00:05:30] 一个好的系统是怎么应对这个世界上面的这种 [00:05:33] essential complexity的 [00:05:35] 对所以其实一定程度上需求工程 [00:05:40] 需求工程 [00:05:41] 就是去直接面对这个系统里面的 [00:05:44] Essential complexity [00:05:44] 他承认软件系统就是复杂的 [00:05:47] 然后我需要不停的和客户去沟通 [00:05:50] 把需求我到底要什么这件事情想清楚 [00:05:52] 不管是你们看到现在的这个就经典的 [00:05:56] 我们说让GPT写了一个需求规格说明书 [00:05:59] 还是你今天可能会用那种map cococ的那种 [00:06:02] grill me的skill [00:06:03] 我其实推荐大家去试用一下 [00:06:06] 你做任何一个小的东西的时候 [00:06:09] 比如说我就要做一个购物车 [00:06:12] 打个比方 [00:06:13] 然然后 [00:06:13] 那GRIMI就会把你所有的细节都给你问一遍 [00:06:16] 你到底要怎么做authentication [00:06:18] 这搞3ICATION [00:06:19] 你是接入一个第三方的那种微信公众号的认证 [00:06:22] 还是这个你要自己做一个login page [00:06:27] 然后这时候其实你的每一个选择 [00:06:30] 在这个需求其实这个还是需求层面的东西 [00:06:33] 你的每一个选择都会都会有很大的影响 [00:06:36] 如果你直接 [00:06:37] 比如说就把这个需求直接交给GPT做 [00:06:40] 说我要login [00:06:41] 那GP就会给你猜一个 [00:06:43] 猜一个的时候他可能会说你需要注册用户 [00:06:45] 好注册用户马上带来的问题是 [00:06:47] 用户要输入用户名和密码 [00:06:49] 这个肯定没问题 [00:06:51] 第一次的时候密码会需要一个confirmation好 [00:06:55] 接下来就是你要不要做一个忘记密码的功能 [00:06:59] 如果你没有忘记密码的功能 [00:07:02] 用户一定会忘掉密码的 [00:07:03] 那对用忘掉用密码的用户来说 [00:07:06] 这就是一个非常糟糕的体验 [00:07:07] 因为它的数据留在里面了 [00:07:08] 他没有办法再 access 了 [00:07:10] 然后如果你要解决这个忘记密码的功能 [00:07:13] 你要么要给他发短信 [00:07:15] 要么给他发邮件 [00:07:16] 好发邮件的时候 [00:07:17] 你用什么来发邮件 [00:07:19] 然后你当你把这些问题不断的往后想的时候 [00:07:22] 你当然是觉得OK [00:07:24] 因为所有人都有一个用户名密码登录 [00:07:27] 所以我的软件也理所应当 [00:07:28] 做一个用户名密码登录 [00:07:29] 但你可能做这个设计或者说需求 [00:07:32] 你做这件事情的时候 [00:07:33] 你就已经进入了一个泥潭对吧泥潭 [00:07:37] 所以也有时候你想 [00:07:38] 那我还是还是用微信的登录比较好 [00:07:42] 微信或者别的 [00:07:43] 你因为你看到现在的 [00:07:44] 比如说你们注册一个网站 [00:07:47] 很多都支持微信或者支付宝登录 [00:07:49] 因为这是一个更方便的模式 [00:07:50] 好那怎么办 [00:07:51] 你要交钱 [00:07:52] 而且个人对这个对个人开发者可能不是很友好 [00:07:55] 你可能需要一个公司实体 [00:07:56] 他才能注册 [00:07:57] 因为你个人承担责任的能力 [00:08:00] 是和公司是不一样的 [00:08:01] 公司某种程度上 [00:08:02] 你需要遵守一定的东西才能才能注册 [00:08:05] 然后这就变得非常非常复杂 [00:08:07] 这仅仅是登录这一件事儿 [00:08:09] 你就有无穷的细节 [00:08:12] 然后进入进去以后又有各种各样的哎 [00:08:14] 其他的啊细节 [00:08:16] 所以在需求里面 [00:08:18] 一方面我们可以这个深度优先 [00:08:20] 就是其实其实这两个是不矛盾的 [00:08:23] 第一个是我们可以深度优先去做一个MVP [00:08:27] 然后这个这个MVP可以快速的让你知道 [00:08:30] 那些最重要的需求功能点 [00:08:32] 你到底是不是满足了 [00:08:33] 满足到什么程度 [00:08:34] 然后这个软件呢比如说需求要不要重做 [00:08:37] 第一第二轮的重做 [00:08:38] 然后以及你的思维方式 [00:08:41] 同样也需要一定的广度优先 [00:08:43] 我们有很多的决策 [00:08:46] 我们的登录框到底是选哪一种方式来实现 [00:08:50] 然后我们的界面到底是怎么样布局 [00:08:53] 第一个首页到底怎么怎么展现 [00:08:56] 这些这些都是需求space里面的里面的东西 [00:09:00] 然后直到总有一天 [00:09:03] 你还是需要把这个需求以某种方式写出来的 [00:09:06] 理论上说就是代码本身就是需求 [00:09:08] 因为我们可以让大语言模型 [00:09:10] 就你打比方 [00:09:12] 你可以把所有的文档都删掉 [00:09:14] 一个实际的项目 [00:09:15] 然后你就让AI去运行代码 [00:09:17] 读代码 [00:09:18] 然后他可以把这些需求的点给还原出来 [00:09:21] 但这个还原出来的这个需求点就会非常非常长 [00:09:23] 他没有一个可能也可以 [00:09:25] 也可以有一个这个从顶到上的理由的设计 [00:09:28] 我们的整个软件系统的设计需求的 [00:09:32] 宗旨是不要搞太复杂 [00:09:36] 要简化复杂性 [00:09:38] 要管理好我们的 [00:09:40] 这个这如果我实现起来麻烦的东西 [00:09:45] 我最好是交给一个第三方来管理 [00:09:47] 那这就是你的 [00:09:48] 你的设计哲学那也可能 [00:09:51] 你的产品它不是这样 [00:09:52] 他就是需要用户名和密码 [00:09:55] 然后你就想要管理这个用户的啊数据 [00:09:58] 比如说你可能就是想要偷这个用户的密码 [00:10:02] 去构建自己的密码库 [00:10:06] 当然这个这个事情肯定是不合规的 [00:10:08] 但在你体量足够小的时候 [00:10:11] 你可能哎你就想要做这件事 [00:10:14] 所以这其实是一个trade-off [00:10:16] 你可能需要在深度优先和广度优先之间 [00:10:18] 达到一个比较好的平衡 [00:10:21] 那就只能做动手做 [00:10:23] 你一方面你去想一个软件系统 [00:10:25] 它为什么做了这个设计 [00:10:27] 然后如果我换另外一个设计 [00:10:28] 它会变好还是变坏 [00:10:30] 我不知道 [00:10:31] 你们上节课看完了教务系统以后 [00:10:33] 有没有想象 [00:10:34] 如果你来实现这个教务系统 [00:10:36] 你会你会怎么做 [00:10:38] 怎么做 [00:10:39] 所以啊这是一些这些需需求工程 [00:10:42] 这个好像没有特别的办法 [00:10:44] 你只能看得多做得多 [00:10:45] 所以我今天当然今天也会讲一些 [00:10:47] 这个具体的系统的实现 [00:10:50] 然后需求之外就是架构架构 [00:10:54] 有两个刚才讲到有两个主要的作用 [00:10:58] 第一个是分解essential complexity [00:11:00] 就有些东西是确实是没有办法避免的 [00:11:04] 但是你有可能把它隔离在不同的模块里 [00:11:07] 然后把这个模块之间做一个很好的接口的 [00:11:10] 切分 [00:11:11] 就比如说数据库就是一个非常好的架构 [00:11:14] 架构设计 [00:11:15] MVC我今天会讲MVC [00:11:17] 当你把数据放到数据库以后 [00:11:19] 你就你就再也不用关心数据是怎么存储的了 [00:11:22] 他给你一个很好的contract [00:11:23] 尤其是你使用比如说云云厂商的数据库的时候 [00:11:27] 他一般都会有给你备份的承诺 [00:11:29] 就是你数据不会丢的 [00:11:30] 就最多就是可能一段时间出问题了 [00:11:33] 然后它可能会降低你的availability [00:11:37] 然后但是你的备份很快就能顶上 [00:11:39] 然后这个数据库访问起来 [00:11:40] 几乎是没有任何的感觉的 [00:11:43] 这样你对数据的可靠性 [00:11:46] 然后数数据访问的原子性 [00:11:48] 那这些你都不用担心了 [00:11:50] 你就可以更好的啊关注在你的业务需求上 [00:11:52] 当然数据库本身是一个通用的system [00:11:55] 你可以你可以把一些 [00:11:57] 你的逻辑就下放给数据库 [00:11:59] 然后同样你在你自己的软件系统里面 [00:12:02] 也可以设计不同的模块 [00:12:03] 比如比如说那个你的信息系统 [00:12:07] 学校最最希望的是打造一个这个全部 [00:12:12] 连打通的信息系统 [00:12:14] 你填任何信息 [00:12:15] 你不需要在这里填一次 [00:12:17] 那里填一次 [00:12:17] 这里填一次 [00:12:18] 那里填一次 [00:12:18] 然后我们花了很长的时间才完成了这个统一 [00:12:21] 身份认证和打通 [00:12:23] 但学校里面其实还有很多的系统 [00:12:25] 可能你们没有用过 [00:12:26] 比如说你们不涉及财务系统 [00:12:28] 你们你们你们不是你们不从学校领工资 [00:12:31] 然后也不管理这个也不管理项目 [00:12:33] 但你们可能会给老师 [00:12:34] 如果研究生的话 [00:12:35] 可能会给老师报销过这个一些单据 [00:12:38] 那就是非常麻烦的一个财务系统 [00:12:40] 财务系统就完没有完全和这个 [00:12:43] 现在的系统打通 [00:12:44] 这也是可以理解的 [00:12:45] 因为财务系统里面有很多领域知识 [00:12:47] 它有大量的领域知识 [00:12:49] 他要把账做平啊接受审计 [00:12:52] 那这些功能本身是在他的领域里 [00:12:56] 现在财务系统和其他的部分 [00:12:58] 还没有很好的连起来 [00:12:59] 比如说实现数据的共享 [00:13:01] 通知的推送啊这些 [00:13:04] 但是其实这是一个比较好比较 [00:13:07] 我们希望得到的一个功能 [00:13:09] 也希望是需求能够需求和架构能够做到做 [00:13:13] 做到这一点 [00:13:13] 然后我们的架构能够 [00:13:17] 对一定程度上就是收缩这个specification和implementation [00:13:21] space的可能性 [00:13:22] 我们的需求 [00:13:25] 需求就是去收缩intent space的不确定性 [00:13:30] 我们说会这个会漂移 [00:13:34] 在这里 [00:13:35] 我们的intent到 [00:13:38] spec到implementation [00:13:42] 那每每一个intent的可能性 [00:13:48] 都会有多种不同的specification的实现 [00:13:51] 然后每一个specification [00:13:53] 又可以有多种implementation的实现 [00:13:55] 所以为了减少这样的漂移 [00:13:59] 我需求实际上是收缩了intent space的不确定性 [00:14:04] 然后架构是收缩了这个spec和implementation [00:14:07] space的可能性 [00:14:09] 就但你一旦确定的时候 [00:14:10] 你就会背上一定程度的这个技术债 [00:14:13] 这个是上一节课的啊内容 [00:14:16] 然后好 [00:14:17] 但还是觉得哦不知道我说了什么 [00:14:20] 不知道我说了什么 [00:14:22] 然后我就发现一件很有意思的事 [00:14:24] 我最近读了又重新读了一些老paper [00:14:28] 然后我发现 [00:14:30] 在你这其实在软甚至软件工程诞生的初期 [00:14:36] 甚至更早的时候 [00:14:37] 我们其实已经有很多的 [00:14:40] 大家们思考过 [00:14:43] 比如说软件或者程序应该怎么构造 [00:14:45] 哪怕这个如果你去看他的原文的话 [00:14:48] 我这里有他的原文 [00:14:57] 这是may note some data structure [00:15:00] 这是 [00:15:03] 叫结构化 [00:15:04] 结构化程序设计 [00:15:06] 我还是用PDF来打开它 [00:15:23] 啊那呵呵 [00:15:26] 很有意思的一本一本老书 [00:15:28] 你看到里面的 [00:15:32] 里面的程序 [00:15:34] 你就知道 [00:15:35] 这不是一个现代意义上的软件工程的书 [00:15:37] 你看到他都是在讲这种相对比较小的程序 [00:15:41] 你打Dijkstra的这个你还能看到这样的流流程图 [00:15:44] 他讲顺序结构 [00:15:45] 选择结构 [00:15:46] 循环结构 [00:15:47] 它的argument是其实我们不需要 goto [00:15:49] 在那个时候他们还在争论 [00:15:50] Go to is harmful [00:15:51] 你只需要用这个顺序选择循环 [00:15:54] 这种diagram的方式 [00:15:56] 就可以表示任何的程序逻辑 [00:15:58] 他们还在argue这种 [00:15:59] 哪怕是一个比较小的小的函数的这个事情 [00:16:04] 然后我们可以找到里面的一些示例的程序 [00:16:09] 让我们来找一个 [00:16:16] 诶这看起来还是非常简单的程序 [00:16:19] 但是这个程序开始出现一些结构了 [00:16:23] 它反正这个书写的很好 [00:16:28] 那我们当然不应该用这种传统的方式 [00:16:33] 来读它 [00:16:34] 我想阅读 [00:16:38] 这个结构化程序设计能先帮我总结一下全书 [00:16:45] 啧对额怎么说 [00:16:48] 读书是额不可避免的 [00:16:52] 对大家来说 [00:16:54] 但是我突然发现 [00:16:56] 在今天读书比以前要变得容易了 [00:17:01] 前提是你用上正确的好用的啊AI工具 [00:17:05] 我因为我就前两天的时候 [00:17:07] 我干了这样的一件事 [00:17:09] 我给每一个我的博士生的建议 [00:17:11] 都我都我都告诉他们 [00:17:13] 你需要去读一些这种经典的文献 [00:17:16] 比如说因因为我自己做这个操作系统 [00:17:19] pr软件工程相关的 [00:17:21] 我就会说这三个领域 [00:17:23] 可能从1980年到2010年是一个golden age [00:17:26] 然后在那个后面以后 [00:17:27] 做的东西都更加就越做越小 [00:17:30] 越做越小 [00:17:31] 越做越小 [00:17:31] 这个算是比较就是越做越窄了 [00:17:37] 那 [00:17:37] 但是在比如说 [00:17:38] 1980年到2010年的时候 [00:17:40] 他还很多这种天马行空的想法 [00:17:42] 在这个系统还不存在的时候 [00:17:44] 他怎么把这些东西给构构建出来 [00:17:47] 然后我就昨昨天吗 [00:17:49] 还是前两天 [00:17:51] 我让我的grog bot [00:17:53] cursor送了一些group bot bot额度 [00:17:55] 他有一台云电脑在后面 [00:17:56] 然后我就说好 [00:17:57] 你去帮我收集这个 [00:17:59] 1980年到这个2010年的 [00:18:01] 这个我觉得值得读的systems的文献 [00:18:05] 然后他就找1000个 [00:18:06] 他就找1000个 [00:18:07] 他大概大概每个领域就找了两三百篇 [00:18:10] 然后我因为我 [00:18:11] 我基本上那些文献大部分都读过 [00:18:13] 然后我就仔细的核对那里面的列表 [00:18:15] 然后我发现他找的列表还是非常好的 [00:18:17] 就是基本上都是 [00:18:18] 我觉得很多文章也是我会推荐的文章 [00:18:22] 然后这就是件非常恐怖的事情 [00:18:23] 然后我现在我现在比如说我开车的时候 [00:18:26] 我就会打开一个GPT [00:18:29] 或者打开一个这个Claude [00:18:31] 我会跟他聊天 [00:18:33] 然后聊天的时候我就可以说好聊一篇paper [00:18:35] 因为那我的paper会像 [00:18:37] 我的paper会在这里被索被索引 [00:18:43] Sorry [00:18:49] 啊对我的paper会被索引 [00:18:52] 然后这样的话 [00:18:54] 我的AI [00:18:54] 其实实际上是可以看到这个paper的全文的 [00:18:58] 然后我只要把paper丢进去 [00:18:59] 然后丢进去以后 [00:19:00] 我就可以比如说一边开车一边 [00:19:03] 今天这个paper是怎么读的 [00:19:04] 我有一次教过大家怎么读paper [00:19:06] 我从从你自己知道的知识开始 [00:19:09] 把你的 [00:19:12] 把你的领域不停的往外往外扩张 [00:19:14] 你要解决什么问题 [00:19:16] 一个你可能你不知道的问题 [00:19:18] 然后用了你什么样不知道的手段 [00:19:20] 然后得到了什么样的结论 [00:19:21] 然后和你所有的知识连成一个网络 [00:19:23] 然后你最终就理解了这个啊这个这个paper [00:19:27] 然后它这里面就有很多 [00:19:30] 我觉得真的是能给人很多启发的啊东西 [00:19:36] 首先这个Dijkstra上来就说这个our inability to do much [00:19:41] 我们人本身刚才我们讲了 [00:19:44] 比如说需求就是复杂的 [00:19:45] 然后我们要做架构 [00:19:47] 但实际上为什么需要做这件事 [00:19:49] 就是因为我们的人的能力是有限的 [00:19:51] 我们是一个参数有限的大模型 [00:19:54] 我们还是承认我们自己是大模型 [00:19:56] 所以这里面i have a very small head [00:19:58] And i had better to learn to live with it [00:20:00] And to respect my limitations and give them [00:20:03] Full credit [00:20:03] Rather than trying to ignore or them [00:20:06] 所以uh这里就有很多有意思的 [00:20:13] 想法 [00:20:14] 他说用抽象加上层次的分解 [00:20:18] Structured programming [00:20:19] 你看起来这个概念已经过时了 [00:20:21] 就是啊我们现在是object orange [00:20:24] 面向对象 [00:20:24] 然后还有很多这种比较fancy的框架 [00:20:27] 你们用前端的 [00:20:28] 可能用React或者View这两两批社区的开发者 [00:20:32] 至今还在圣战 [00:20:33] 就像以前这个Vim和Emacs的这个开发者 [00:20:36] 还在圣战 [00:20:37] 这呵呵虽然他们两个都越来越越像了 [00:20:41] 但你看到这些今天所有的这些东西在过去 [00:20:45] 其实他们已经想了想过一遍 [00:20:48] 他需要用一个层次化的分解方式 [00:20:51] 把人不能依次理解的东西 [00:20:53] 变成人可以逐层理解的东西 [00:20:56] 然后这里面有很多哎 [00:20:57] 我真的是特别喜欢的观点 [00:20:59] 比如说我看我有没有slice对 [00:21:02] 有一个mental aids [00:21:04] 枚举归纳和抽象这个part one [00:21:08] 最重要的 [00:21:08] 你就我们的人脑可以干的 [00:21:11] 就是这三件三件事情 [00:21:14] 你如果去看那里面的案例 [00:21:16] 他今在今天看来都是小程序 [00:21:18] 刚才带大家啊看过 [00:21:20] 但是他做了一个比喻 [00:21:22] 他说我们要做一个程序 [00:21:25] 或者你就想象成做一个软件系统 [00:21:27] 你就要做你做的其实是一个necklace [00:21:29] 它要做一个项链 [00:21:31] 然后项链上的珍珠 [00:21:32] 每个项链上的小珍珠把它串起来 [00:21:34] 站起来得到一个软件 [00:21:36] 这个比喻其实就是在分解 [00:21:37] 就是告诉你要分解 [00:21:40] 复杂性它怎么样 [00:21:45] 把一个人类不能理解的东西 [00:21:47] 变成人类可以逐个理解的东西好 [00:21:50] 然后然后你看这样一本书 [00:21:52] 你就这样想 [00:21:53] 老师可能会在课上推荐大家 [00:21:56] 就也许是比如说我在课上推荐大家读一本书 [00:21:59] 也许是我们的你们看某一本教科书的时候 [00:22:04] 后面有一个角注说诶我的这个想法 [00:22:07] 教科书的想法来自于啊这一本书 [00:22:10] 我不知道你们有没有留意过 [00:22:12] 如果你们读一些比如说经典的这个programming language [00:22:15] 或者软件设计的书 [00:22:17] 他可能都会引用这个对 [00:22:19] 因为这个太有名了 [00:22:20] 都会引用到这本书 [00:22:21] 然后在引用这本书的时候 [00:22:22] 你们有两种选择 [00:22:23] 一种是这个 [00:22:25] 当然我觉得我读书的时候 [00:22:27] 都基本上也都必须要面临这个选择 [00:22:29] 就是看一下 [00:22:32] Dijkstra好像写过这本书 [00:22:34] 忽略它 [00:22:35] 因为如果我要把这本书找挖出来 [00:22:38] 然后再去读它 [00:22:40] 这件事情对我来说会浪费太多的时间 [00:22:42] 但对大家来说 [00:22:43] 你们现在就可以 [00:22:44] 一直是可以直接去 [00:22:48] 用原始的最原始的第一手信息去 [00:22:53] 按照你自己想要的方式来处理 [00:22:57] 在这里其实还是有很多东西 [00:23:00] 你看他讲了class和object [00:23:02] 这是1972年 [00:23:04] 1972年class and object [00:23:07] 然后协程他考虑的是怎么样构造 [00:23:11] 各种各样的概念层次 [00:23:13] 他为什么要发明协程 [00:23:15] 协程协程和线程其实很像我 [00:23:18] 我上课的时候线程打什么 [00:23:20] 比方线程就比方成人 [00:23:22] 然后这个shared memory就是物理的空间 [00:23:26] 所以人和人之间是协作的 [00:23:28] 每个人都有他自己的memory [00:23:31] 我自己有自己的memory [00:23:32] 我知道我做到哪一步了 [00:23:33] 我今天要干什么 [00:23:34] 明天要干什么 [00:23:35] 但同时我在这个shared space里面 [00:23:37] 又有一些共享的事情 [00:23:38] 我们要合作完成完成任务 [00:23:40] 那我们人和人之间的通信 [00:23:42] communication就成为了一个协议 [00:23:45] 这样我就可以把复杂性隔离在 [00:23:47] 我可以把一个工作 [00:23:49] 我可以把一个工作分解成333条线 [00:23:55] 然后他们之间可以通过一个数据结构来 [00:23:58] 或者一个什么东西来共享 [00:24:02] 他们可以不断的向前进度进展 [00:24:05] 可以不断的向前 [00:24:06] 然后他们要互相通信来完成 [00:24:08] 完成完成完成整个任务 [00:24:11] 然后他举了一些这样的这个博弈下棋的例子 [00:24:15] 还有啊编编译期的例子 [00:24:20] 然后有一个概念层次的 [00:24:23] 金字塔 [00:24:25] 就你看最终你如果今天去看那些好的设计的 [00:24:30] 好的软件 [00:24:31] 软件系统的话 [00:24:32] 这个我们做的事情 [00:24:37] 无非就是这个书里面 [00:24:38] 其实已经已经讲过的啊事情 [00:24:42] 所以我们让AI来带 [00:24:44] 我们 [00:24:44] 就今天假设大家前面的东西都已经 [00:24:47] 多多少少知道 [00:24:47] 但其实其实你们不知道 [00:24:49] 你们你们可能只是在上操作系统课的时候 [00:24:51] 第一次听说了coroutine [00:24:52] 然后coroutine在在今天的意义下 [00:24:56] 更多的是一个为了性能实现的轻量级的线程 [00:24:59] 比如说go的那种 [00:25:01] 协程的模型 [00:25:03] 它就是为了让你能够像线程一样用的很开心 [00:25:05] 同时他的那个每一个协程的 [00:25:08] 切换和bookkeeping的开销都非常小 [00:25:11] 来因为你心里已经是现成模型了 [00:25:15] 那个时候你看几乎没有它没有现成的 [00:25:17] 现现成的模型 [00:25:19] 但是如果你回到他涉及这些机制的基础上 [00:25:24] 就回到那个时间点 [00:25:26] 你会知道他们其实是在应对 [00:25:30] 软件世界里面的 [00:25:31] 复杂性好 [00:25:33] 那我们就试一试 [00:25:35] 试一试啊试一试 [00:25:38] 因为我也没有仔细读过 [00:25:44] 应该是part three的 [00:25:49] 面向上课的本科生 [00:25:54] 看一看他对这件事情的理解 [00:25:57] 这个时候他会去读原文 [00:25:59] 然后然后这个原文哪怕 [00:26:01] 你就让它翻译 [00:26:02] 然后去掉一些这个你觉得不太必要的部分 [00:26:08] 这都是很好的 [00:26:14] 咳咳把数据加操作打包成一个概念 [00:26:18] 再用前缀机制把概念一层一层搭起来 [00:26:22] 这就是抽象 [00:26:22] 所谓的抽象 [00:26:23] 你要做一个模块 [00:26:24] 不管不管加到什么名字 [00:26:25] 一个模块也是用这个class和method [00:26:29] 你都需要把这个实现内部的复杂性 [00:26:32] 比如说一个选课 [00:26:35] 或者111门上课 [00:26:37] 这个上课这件事和 [00:26:41] 系统的其他部分要一定程度上隔离开来 [00:26:44] 我可以比如说和教室的管理 [00:26:47] 什么一个教室一个课程 [00:26:50] 然后他之间怎么关联 [00:26:52] 要适当的做这样的分类 [00:27:02] 诶他讲了很多比较有意思的观察 [00:27:05] 他说学生写的程序为什么这么烂 [00:27:08] 是因为一次要同时在脑子里面的东西太多 [00:27:11] 这个时候你想一想 [00:27:12] 你们平时哪怕是写OJ题 [00:27:16] 什么样的题 [00:27:17] 你觉得挑起来最困难可能不是模拟题 [00:27:21] 可能是那种需要一点这个数学性质 [00:27:25] 比如说动态规划这样的题 [00:27:28] 你们调这个题 [00:27:29] 如果你第一次就对了 [00:27:30] 样例对了 [00:27:32] 然后数据也对了 [00:27:33] 可能还挺好的 [00:27:33] 如果你一个一两百行的动态规划 [00:27:35] 你 [00:27:36] 然后你这时候错了 [00:27:38] 你根本就不知道怎么挑起 [00:27:41] 这是这是为什么 [00:27:42] 是因为就这个程序动一个动态规划的程序 [00:27:46] 会比其他程序更难分层 [00:27:48] 因为你想如果是一个模拟 [00:27:50] 你多多少少还可以把模拟的几个大的部分 [00:27:54] 给分开 [00:27:55] 封装成一个class [00:27:56] 然后这个class你可以单独测试 [00:27:57] 单独单独调试 [00:27:58] 而且你很容易把那个程序的一个中间状态 [00:28:02] 映射到一个你能理解的 [00:28:04] 你能理解的一个这个 [00:28:07] 实际物理有物理含义的状态上 [00:28:10] 而如果你是调一个像动态规划这种 [00:28:12] 两三百行的动态规划好 [00:28:14] 那就是灾难 [00:28:14] 因为它中间是一个数学 [00:28:16] 是一个纯粹的数学表示 [00:28:18] 然后这时候你就可以想 [00:28:20] 你有没有可能把这个动态规划的这个层次 [00:28:25] 或者是这个状态改成 [00:28:27] 你能够以我们熟悉的方式去观测它 [00:28:31] 这事情实际上是可以扳倒的 [00:28:33] 就因为我们每一本书上写 [00:28:34] 比如说写动态规划都是什么dp[i][j][k] [00:28:39] 然后然后你们写的时候也是个 dp[i] [00:28:41] 然后就for i four j four k [00:28:42] 然后你只要有一个有一个小的地方弄错了 [00:28:46] 那整个就全部都错了 [00:28:47] 而且错有的时候他不会crash [00:28:49] 你crash反而好找 [00:28:50] 你知道这个地方越界了 [00:28:51] 如果他不crash [00:28:52] 他就算出一个错的值 [00:28:54] 就你转移的时候弄错了 [00:28:57] 诶你就错了 [00:28:58] 但实际上在我们谈论这个dp[i][j][k]的时候 [00:29:02] 我们其实是有一个问题的 [00:29:03] 是有个优化 [00:29:04] 一般来讲是一个优化问题 [00:29:07] 有个优化问题 [00:29:08] 然后这个优化问题本身是有一个图结构的 [00:29:10] 也可能是个图或者别的什么样的结构 [00:29:12] 然后这个动态规划 [00:29:13] 本质上是在一个状态空间上面做 [00:29:17] 它有一个这个无后效性 [00:29:20] 一个有向 [00:29:22] 一个有向无环图 [00:29:23] 哎这就是一层抽象 [00:29:25] 然后在我们写dp[i][j][k]的时候 [00:29:27] 其实把这个抽象给扔掉了 [00:29:29] 你不你 [00:29:30] 虽然我们在思考问题的时候 [00:29:32] 是工作在这个抽象层级上 [00:29:34] 但是你在写代码的时候把它扔掉了 [00:29:37] 所以其实你可以做的是 [00:29:38] 把这个概念状态的概念给放回来 [00:29:41] 你可以知道dp[i][j][k] [00:29:45] 它到底代表什么含义 [00:29:47] 然后你可以把它可视化 [00:29:49] 甚至把它可视化出来 [00:29:50] 甚至可以把这些关联的状态和迁移给打印出来 [00:29:54] 比如说你可以 [00:29:57] 你可以实现一个这个 [00:29:59] 比如说get [00:30:06] Of i j k [00:30:08] 然后他可以返回一个你的状态的迁移 [00:30:12] 所有的所有可能的迁移 [00:30:14] 然后你做了这层抽象以后 [00:30:18] 当然这个可能还是不够好 [00:30:20] 你可以用一个更好的抽象 [00:30:21] 比如说state来替代它 [00:30:24] 这样你一层一层的做抽象 [00:30:25] 这个程序你就可以调试了 [00:30:26] 然后你调完了以后 [00:30:28] 你可以把这个再改写成等价的更高效的形式 [00:30:33] 你看这也是一个一个小的小的小的架构 [00:30:37] 当然这是这是小小的例子 [00:30:39] 然后你各种各样更大的粒子的时候 [00:30:44] 他举了一些像如果你要模拟并行的时候 [00:30:47] 你就可以创造一个模型 [00:30:50] 创造一个模型 [00:30:50] 你不需要就是你模拟变形 [00:30:53] 你可以强行的直接直接模拟 [00:30:55] 你说有个状态机 [00:30:56] 状态机 [00:30:57] 然后直接模拟 [00:30:58] 然后你也可以做一个这个coroutine的抽象 [00:31:02] 这样啊你只要做出合适的抽象 [00:31:06] 你看hold weight [00:31:07] activate和simulate [00:31:09] 他做出一个合适的抽象 [00:31:11] 你就可以去模拟模拟时间 [00:31:14] 和你直接上手去写一个模拟 [00:31:16] 是啊是很不一样的 [00:31:19] 这个这个可能我没有 [00:31:21] 很长就停下 [00:31:23] 比如说你如果对这个地方不太明白 [00:31:24] 我相信 [00:31:26] 因为我有这个概念了 [00:31:27] 我知道他在说什么 [00:31:29] 你们如果没有 [00:31:30] 你就可以继续的 [00:31:32] 继续的追问 [00:31:33] 甚至是 [00:31:34] 你可以跟比如说g ChatGPT开一个语音的赛车 [00:31:37] 因为我最近发现发现这个feature很好用 [00:31:39] 大概就是从今年开始 [00:31:41] ChatGPT的实时语音模型 [00:31:43] 可以在在后台调用一个更好的思考的模型 [00:31:51] 然后并且它可以保持和你对话 [00:31:53] 这是一个很好的feature [00:31:54] 你可以 [00:31:55] 你可以把论文丢进去 [00:31:56] 然后你一边问他一些简单 [00:31:57] 他可以直接立即回你 [00:31:58] 然后一边你问他一个 [00:31:59] 比如说哎你能不能给我想一个打比方 [00:32:05] 符合现代软件前端的例子 [00:32:11] 完全取决于你的提示词 [00:32:13] 这取决于你的想象力到到到什么什么程度 [00:32:17] 我不知道DPC可能给什么 [00:32:18] 我完全完全不知道 [00:32:19] 但是如果你有一个更好的模型的话 [00:32:23] 它 [00:32:29] 唉你看好这个React scheduler和Fiber [00:32:34] Fiber架构 [00:32:35] 他找到了这个这个对应好 [00:32:38] 那如果我可以再上升一步 [00:32:41] 比如说如果我不想谈具体的例子 [00:32:44] 只是 [00:32:50] 抽象的想法有什么 [00:32:54] 现在最经典的 [00:32:59] 预训练的模型就它没有web search [00:33:01] 它没有web search [00:33:02] 如果预训练的模型 [00:33:03] 它就会从脑子里面找到他的那个直觉 [00:33:06] 但如果你有web search的话 [00:33:08] 你可以 [00:33:09] 要求GPT去调研100篇1000篇论文 [00:33:11] 或者从我的那个池子里面的一篇千篇论文里面 [00:33:13] 选一个 [00:33:14] 选一个最好的 [00:33:15] 符合这个的 [00:33:17] 他找了一些有意思的 [00:33:20] UNIX关系数据库 [00:33:24] interface trade protocol TCP IP [00:33:29] 都是告诉你要做一个好的抽象 [00:33:33] 很有意思 [00:33:33] 这几个例子我今天上课都会讲 [00:33:35] 所以某种程度上 [00:33:37] 呵我们还是还是达成了一定的啊一致对 [00:33:42] 就我觉得 [00:33:43] 可能这门课我讲讲什么东西都是不够的 [00:33:47] 因为我在这一门课上 [00:33:48] 我没有办法把那1000篇 [00:33:50] 比如说我刚才讲的system softw [00:33:52] 你读PhD的话 [00:33:53] 在我这里读的话 [00:33:54] 你得把它啊把它多多少少都读了 [00:33:57] 这个还是个很恐怖的事情 [00:33:59] 然后这个但是你们得到的启发是 [00:34:09] 读书可能也许没有那么的困难 [00:34:13] 然后我就在找这些的时候 [00:34:14] 我就在在准备今天的课的时候 [00:34:16] 我就又去找了一些有点老的 [00:34:19] 有点老的材料 [00:34:21] 就那刚才那是1972年 [00:34:22] 然后就想如果要到一九 [00:34:24] 比如说80年1990年的时候 [00:34:27] 我们又怎么来看这件事 [00:34:29] 然后我就找到了一个很有意思的 [00:34:31] talk这个他是Scheme的设计者 [00:34:36] 对他是Scheme的设计者 [00:34:38] 也是这个Java language specification的合作合作者 [00:34:42] 如果你们读过 [00:34:43] 我读过那个Java加Java language和加virtual machine specification [00:34:47] 它会告诉你语言和虚拟机是怎么 [00:34:50] 怎么怎么设计的 [00:34:51] 然后他在OOPSLA的时候 [00:34:53] 就是program language里面的一个讲object-oriented的的 [00:34:58] 顶顶级会议 [00:35:00] 他在98年的时候给了一个keynote [00:35:01] 那个时候他已经当然已经是功成名就了 [00:35:04] 作为那个时候最火的语言之一的 [00:35:07] 这个设计者 [00:35:09] 他写了一篇文章叫做growing a language [00:35:13] 那我们也来 [00:35:23] Article [00:35:31] Growing language [00:35:34] 我让他直接帮我打开好 [00:35:40] 我想要阅读 [00:35:56] 是这样 [00:35:57] 他打开PDF [00:35:59] Ok [00:36:04] 对你第一次看到他的时候 [00:36:07] 就是我们作为后人 [00:36:09] 看到这里有一个PDF [00:36:11] 如果你第一次看到他的时候 [00:36:13] 你会说 [00:36:14] 这是什么 [00:36:17] Most of the time [00:36:18] I do not read my talks [00:36:20] 这个你没理解 [00:36:23] 就如果你直接看到它的时候 [00:36:24] 你没有理解 [00:36:25] 然后你往后往后翻翻 [00:36:26] 翻到一个 [00:36:27] 这个好像是个什么东西 [00:36:29] 然后就把它关掉了 [00:36:31] 你因为你看到这么多字 [00:36:32] 十几页 [00:36:33] 你想你会停下来吗 [00:36:36] 停下来吗 [00:36:37] 所以你应该应该停下来 [00:36:40] 这个talk非常非常有趣 [00:36:43] 他是一个表演性的演讲 [00:36:50] 他是写 [00:36:52] 所以他说i have to read [00:36:53] 他想要读出来 [00:36:54] 他全篇都只用单音节英文词 [00:36:58] 然后遇上所有的双音节以上的词 [00:37:00] 必须当场定义 [00:37:02] 就跟我说写一个藏头诗 [00:37:04] 或者写一个全是一个音的这个文章一样 [00:37:08] 他就做了一个这个非常哼非常炫技的这个 [00:37:12] 所以你看到这就这个品味和AI s stop [00:37:14] 就是不一样的 [00:37:15] AI s stop写不出这种东西 [00:37:16] 又或者说诶这你需要至少从今天的角度来看 [00:37:20] 你还需要人类的创造力 [00:37:22] 你才能想到这件事 [00:37:23] 而且他用这个方式来讲 [00:37:26] Growing a language [00:37:27] 就是如果一个语言 [00:37:28] 这个language是双关的 [00:37:29] 你如果要让一个语言能够使用 [00:37:34] 他刚开始的时候是一个全篇只用单音节英文词 [00:37:37] 然后它他会发现这个语言很难用 [00:37:40] 当然是说对人类来说很难用 [00:37:42] 我觉得如果是AI的话 [00:37:43] 他应该能够做出 [00:37:45] 就我觉得今天我可以写一个东西来challenge啥 [00:37:47] 就这个文章 [00:37:49] 就全部是用单音节的英文词写的 [00:37:51] 但是它读起来就完全没有任何问题 [00:37:53] 他讲的是如果遇到双音节以上的词 [00:37:56] A woman machine [00:37:58] Vocabulary [00:37:58] Programming [00:37:59] Computer [00:37:59] 必须当场定义 [00:38:01] 然后这样他就会遇到一个就会遇到哎 [00:38:06] 我不打开它了 [00:38:06] 它就会遇到瓶颈 [00:38:08] 你 [00:38:10] 想要定义一个复杂的东西 [00:38:12] 想要讲清楚任何东西都变得很复杂 [00:38:16] 然后他就用这样的一个 [00:38:20] 很有趣的开场讲了编程programming language里的这个问题 [00:38:26] 所有的编程语言相对于英语 [00:38:28] 相对于用户需求都很小对吧是的 [00:38:33] 因为它需编程语言 [00:38:34] 需要有一个formal semantics [00:38:36] 它要有形式语义 [00:38:37] 他要如果这个语言变得非常大 [00:38:39] 语言里面有这种woman这种概念 [00:38:42] 那它的形式语义是什么 [00:38:44] 你定义不出来 [00:38:45] 而必须有形式语义的东西才能在计算机上执行 [00:38:48] 所以编程员都相对来说比较小 [00:38:52] 然后如果你的编程语言 [00:38:55] 在设计的时候是一个minimal design [00:38:57] 就最最最最最最最小 [00:38:59] 比如说你没用过brain [00:39:02] 那就完全无法写 [00:39:05] 也没有效 [00:39:06] 所以 [00:39:13] 你们你们笑 [00:39:16] 他应该是知道他有他有语料的 [00:39:18] 基本上就是个图灵机 [00:39:19] 就是他把图灵机直接编码成 [00:39:24] 诶他竟然还要啊写工具 [00:39:27] 对他语言基本上就是这样的一串一串东西 [00:39:30] 它有一个指对理解成类似图形结有个指代 [00:39:34] 然后你可以往左往右 [00:39:34] 然后做加法可以判断 [00:39:38] 比如说这是一个hello world的程序 [00:39:40] 对它最小 [00:39:42] 它满足最小 [00:39:42] 但当然人类不可读 [00:39:43] 实际编程语言是 [00:39:46] 当然是不会那么那么的离谱 [00:39:49] 还是可以用的 [00:39:53] 但是可以用的就是比如说C [00:39:56] 你们C其实是一个设计的非常经典的啊语言 [00:40:00] 但是C也带来了很多的设计上的上的问题 [00:40:04] 比如说它就没有运算符的重载 [00:40:06] 它没有运算符重载 [00:40:08] 你在写表达这种 [00:40:10] 比如说你要表达一个往队列里面放东西 [00:40:15] 如果你们写C写的多的话 [00:40:17] 就知道这是一件多么恐怖的事情 [00:40:19] 然后你再看那个 C里面有很多 [00:40:21] 比如说不是这种东西 [00:40:28] qsort [00:40:29] 你们可能学过 [00:40:31] 因为学过C语言 [00:40:32] 就是设计的非常糟糕的 [00:40:35] API对它有一个int的compare的指针 [00:40:38] 然后他需要这个这个签名竟然是constant void [00:40:42] 然后哦anyway这个到一个数组里面 [00:40:46] 还需要这个nmemb和size两个两个参数 [00:40:51] 就C没有这个没有这样的能力 [00:40:55] 让你自定义比较好的语言抽象 [00:40:59] 所以他在表达任何东西的时候 [00:41:00] 都会遇到遇到困难 [00:41:02] 自己看这两件事情 [00:41:04] 在现实意义上 [00:41:05] 就他用单音节词去需要定义双音节词的时候 [00:41:09] 就遇到了啊困难 [00:41:11] 小的语言好移植就好好用 [00:41:13] 但是满足不了各种各样膨胀的需求 [00:41:17] 但是一次设计好一个大语言的体量大 [00:41:22] 交付慢 [00:41:23] 就会在成功前 [00:41:24] 被每一个某一个小而丑的语言取代 [00:41:26] 所以你看C就是典型非常典型的这个成功者 [00:41:29] 他在有些方面很成功 [00:41:31] 因为我们喜欢C [00:41:33] 很大程度上是因为它直接和机器是一一对应的 [00:41:37] 它没有额外的抽象 [00:41:38] 所以所以这就是一个 [00:41:40] 可以移植到几乎任何地方的编程语言 [00:41:43] 然后单呢是在人人力写代码的年代 [00:41:47] 那我们写一个编译器是一个非常复杂的 [00:41:50] 复杂的事情 [00:41:51] 所以在这个意义下 [00:41:53] 他做了一个编程语言的架构 [00:41:56] 你看我今天讲的是架构什么样的 [00:42:00] 就当我们谈架构的时候 [00:42:02] 大家都会觉得哦软件是个架构 [00:42:04] 我要用什么样的数据库 [00:42:06] 我要设计什么样的class来表示 [00:42:08] Student [00:42:09] 设计什么样的class来表示course [00:42:10] 这个叫叫软 [00:42:11] 当然这是软件架构的一部分 [00:42:13] 实际上你的编程语言里面有什么 [00:42:17] 这件事情本身也是架构 [00:42:19] 我前面讲那个QR code2维码的意思是 [00:42:22] 我就创建了一个小的编程语言 [00:42:24] 这也是一个一个架构 [00:42:26] 所以这个架构提了一个非常深刻的问题 [00:42:29] 说这个出路不是二选一 [00:42:31] 而是生长 [00:42:33] 也就是说你的这和其实软件的需求又是 [00:42:38] 我前面上第一次课的时候 [00:42:39] 我就讲一个好的软件 [00:42:41] 它能够或者软件的设计 [00:42:43] 它能够抵抗需求的变更 [00:42:46] 当需求变化 [00:42:47] 世界发生变化的时候 [00:42:49] 你一定程度上 [00:42:50] 你在现在可以前瞻性的看到一点 [00:42:53] 这个未来会发生什么 [00:42:55] 如果你只是针对现在设计 [00:42:57] 你可能没有考虑到这个软件应该怎么样的生长 [00:43:01] 它有可能就会陷入一个 [00:43:03] 一个技术债的啊泥潭 [00:43:06] 你需要有这个 [00:43:10] 生长的能力的架构是啊是最好的 [00:43:16] 对 [00:43:17] 然后他举了一些例子 [00:43:18] 比如说一些老的语言 [00:43:19] 这些语言你们应该都没有没有用过的 [00:43:21] 我还用过帕斯卡 [00:43:23] 然后C和从C到C++是一个啊长出来 [00:43:26] 但我觉得C++长得并不好 [00:43:28] 从你看从从C到C++ [00:43:33] 比如说模板 [00:43:34] 模板是一个非常非常重要的架构 [00:43:38] 但是在这你看他为了实现各种各样的功能 [00:43:43] 在C++十一十四十七之前 [00:43:45] 这个模板被滥用到了 [00:43:47] 就boost里面那些模板没有人能看得懂 [00:43:49] 大家不知道 [00:43:50] 那是就对 [00:43:51] 尤其对于同学来说 [00:43:52] 大家都不知道那是什么 [00:43:53] 你需要花很长时间才啊才能理理解他们 [00:43:58] 他模板是一个很好的机制 [00:44:00] 但是长出了一些比较奇怪的啊奇怪的东西 [00:44:03] 所以他给Java [00:44:04] 你看他给Java提供了一些啊一些建议 [00:44:08] 他主主张只加三样这个圆工具泛型泛型很重要 [00:44:11] 没有泛型 [00:44:12] 你构造抽象的时候 [00:44:14] 你写得像C那样 [00:44:16] 或者早期的Java那样那样 [00:44:19] 到处都有强制类型转换 [00:44:21] 然后运算符重载诶 [00:44:24] 这个好像Java到至今都没有没有没有加入 [00:44:30] 轻量用户自定义类 [00:44:31] 然后这个这个这个现在是有的 [00:44:41] 他讲了这样的一个架构的故事 [00:44:44] 那我们也可以问 [00:44:57] 我们每一个同学都有自己的偏好 [00:45:00] 然后你们每一个同学问出来的问题 [00:45:02] 就是不一样的 [00:45:03] 然后不一样的时候就会有额 [00:45:07] 不一样的结果 [00:45:12] 这场演讲是一条关于grow ability的架构哲学 [00:45:27] 扩展必须是啊一等一等公民 [00:45:32] 扩展必须是一等公民 [00:45:36] 而且扩展可以之间可以组合 [00:45:38] 这就是一个可生长的架构 [00:45:41] 它不是说好我有一个需求清单 [00:45:44] 然后我把这个需求清单全部都实现了 [00:45:46] 这叫正确 [00:45:47] 我们做OJ有一个就有一点 [00:45:49] 从你们进大学开始 [00:45:52] 疯狂的做各种各样的OJ [00:45:54] 他就把你们训练成了解题的机器 [00:45:56] 因为做OJ是不需要任何扩展的 [00:45:58] 你这个题对了 [00:45:59] 你就这个代码是不需要维护 [00:46:03] 你用后即扔的代码 [00:46:04] 你就会有一种不自觉的 [00:46:06] 像这种用户集扔去啧走的倾向好 [00:46:11] 然后然后就把你们这 [00:46:13] 在这个方面的思考变少了 [00:46:16] 然而某种程度上这门课会拉回来一点 [00:46:19] 扩展必须是一等公民给言语和组合机制 [00:46:25] 而不是用力清单 [00:46:27] 我们可以照着需求清单去写 [00:46:30] 写实现 [00:46:30] 但是如果我们把这个需求分解成跳出问题 [00:46:33] 看问题的几个概念 [00:46:36] 然后这个概念又可以变成具体的 [00:46:39] 比如说领域里面的东西好 [00:46:41] 然后可以 [00:46:43] 可以组合 [00:46:44] 那这可能就是一个对的 [00:46:47] 对的设计好 [00:46:48] 我就不不继续看了 [00:46:50] 你们你们最开始的时候 [00:46:53] 比如说你们刚开始大二大三的时候读这些东西 [00:46:55] 肯定是非常慢的因为你因为刚才我说 [00:46:58] 我们是从你已知的知识开始往外往外推 [00:47:03] 我现在读论文就很快 [00:47:04] 是因为我的已知的知识相对相对来说比较多 [00:47:06] 而且AI可以帮我帮我整理 [00:47:08] 那你再建立一个比较好的comcomputer science的核心 [00:47:12] 核心知识之前 [00:47:13] 你都会经历一个比较痛苦的过程 [00:47:15] 就是你不理解为什么 [00:47:17] 好消息是你可以都跟AI来询问这个 [00:47:21] 各种各样的啊事情对好 [00:47:25] 所以我觉得我有个comment就是有太多的宝藏 [00:47:29] 被埋在这个快速发展的时代里面了 [00:47:32] 我们我就大家同学们 [00:47:35] 到了进组进组了 [00:47:38] 大家一定是 [00:47:39] 我们最近又发发发表了一篇什么state of the [00:47:41] art的文章 [00:47:42] 师兄又发表了一篇self的文章 [00:47:44] 然后我觉得某种程度上 [00:47:46] 大家的品味就被这些海量的这个人类 [00:47:50] slop和AI slop都污染了 [00:47:52] 就比如说CSDN上面的东西 [00:47:54] 防止自学的教科书 [00:47:56] 然后就这里看到我今天举的两个只是非常随 [00:48:00] 非常随意的例子 [00:48:02] 可能这些经典就有成百上千这样 [00:48:08] 我觉得就在在我到我读书的时候 [00:48:12] 从我比如说本科高年级到这个 [00:48:15] 我博士毕业的这段时间里面 [00:48:16] 我可能我是不可能把他们都读完的 [00:48:19] 所以我只读了他们其中的一部分 [00:48:21] 我算是读书读的比较多的人 [00:48:23] 也只是很少的一部分 [00:48:25] 然后有一些自己的想法 [00:48:28] 但是在今天你们每一个人都可以 [00:48:31] 每个人都可以快速的 [00:48:33] 而且是原汁原味的去了解到里面的 [00:48:38] 作者想要传达的原始的啊想法 [00:48:41] 所以我就非常期待一个这个大学毁灭计划 [00:48:44] 我们我们有没有可能 [00:48:46] 从就把比如说大一的第一门课program language [00:48:51] 就把知识点全拆散了 [00:48:52] 我们我们不用一本什么 [00:48:56] 甚至是这个教科书 [00:48:57] 可能是你们上课的这个老师写的教科书 [00:49:00] 我们不用这个教科书 [00:49:01] 我们把这个原始的知识全部都break down [00:49:06] 把1970年 [00:49:07] 1980年 [00:49:07] 1990年那些经典的东西全部都拆拆开来 [00:49:10] 然后重新串联起来 [00:49:11] 为你生成一个教材 [00:49:14] 我觉得这个这个是一个挺好挺好玩的 [00:49:18] 挺好玩的事情 [00:49:19] 对好 [00:49:20] 这几个是架构的 [00:49:22] 我从growing a language [00:49:26] 他说一个能够生长的架构才是最好的架构 [00:49:30] 那这个架构设计的空间 [00:49:34] 到底有哪些经典的案例 [00:49:37] 那第一个当然就是大名鼎鼎的UNIX philosophy [00:49:40] 三条原则 [00:49:41] 每个工具 [00:49:42] Do one thing do well [00:49:43] 然后right programs work together [00:49:46] Write programs to handle text streams [00:49:48] 第三条其实是一个非常精髓的设计 [00:49:52] 因为文本接口统一了人和程序 [00:49:55] 程序和程序的接口 [00:49:57] 人如果只是binary的字节流的话 [00:50:01] 对人来说是很不友好的 [00:50:02] 所以你们在用数据库的时候 [00:50:05] 你们都知道数据库好 [00:50:06] 但是用数据库的时候 [00:50:07] 你总是觉得隔了一层纱 [00:50:10] 因为就是你每一次查询任何东西 [00:50:13] 都需要一个SQL query [00:50:14] 然后这个SQL query对你来说是indirect的 [00:50:16] 而你只是想要一个立即的东西 [00:50:18] 你人不是很适应 [00:50:20] 在做任何事的时候都要加一层in indirection [00:50:24] 当然模型可以 [00:50:26] 比如说你可以让模型每说一句话以后 [00:50:29] 都说一个喵 [00:50:30] 他能遵循指令 [00:50:31] 但如果是人来遵循这个指令的时候 [00:50:33] 人就会觉得非常非常痛苦了 [00:50:35] 而这个想法其实在1964年的时候 [00:50:38] 就已经发现了 [00:50:39] 然后我们可以同样是原文 [00:50:43] 这时候你就看到你有一个AI可以帮你收集资料 [00:50:47] 一个agent能帮你收集资料 [00:50:49] 然后并且能够帮你总结原文的时候 [00:50:51] 这就是这就是原文 [00:50:53] 这个 [00:50:56] 四个要点 [00:50:56] 第一个像接花园水管一样连接程序到管道 [00:51:01] 在应该是1983年的时候引入引入UNIX的 [00:51:04] 但其实你看这一条 [00:51:05] 在那个时候就已经有了这个想法了 [00:51:10] 然后你看他后面有很多这个其他的这个 [00:51:14] 关于操作系统设计的 [00:51:16] 非常经典的想法 [00:51:18] 动态的动态链接这个库的索引 [00:51:23] 然后你看这些 [00:51:23] 对今天的这个系统设计 [00:51:26] 都是有非常深远的 [00:51:29] 深远的影响 [00:51:31] UNIX然后UNIX就是依靠一套UNIX世界里面的语言 [00:51:40] 当然你们也可能看过那个叫UNIX hater sky [00:51:42] 他就各种吐槽UNIX里面的这种设计的 [00:51:45] 设计的不好 [00:51:46] 但是就是一套统一的标准 [00:51:48] 所有的命令行工具都遵循 [00:51:50] 大致一样的命令行参数 [00:51:52] 这样人就可以理解了 [00:51:54] 人很好理解 [00:51:55] 然后 [00:51:57] 而且他们输出的这个格式也尽可能的保持一致 [00:52:01] 因为当有几大工具 [00:52:03] 比如说sed、awk这种工具 [00:52:05] 奠定了技术以后 [00:52:06] 那 [00:52:07] 程序的输出也开始向这几个工具友好的方向 [00:52:11] 同同时对人也对工具友好的方向来 [00:52:15] 继续以及这个退出状态 [00:52:19] Exit status [00:52:20] 它可以在程序出问题的时候 [00:52:22] 及时的告诉另外一个程序好 [00:52:26] 就这几个加上管道 [00:52:28] 这就是一个非常棒的机制 [00:52:31] 比如说当我们需要 [00:52:36] 比如说你们要设计一个软件 [00:52:38] 然后这个软件需要一个打开文件的菜单 [00:52:43] 那这个打开文件的菜单 [00:52:45] 要么就是他自己实现一个 [00:52:47] 要么就是使用操作系统的 [00:52:49] 但如果你对这两个都不满意 [00:52:52] 你有办法吗 [00:52:53] 没有办法 [00:52:55] 你想 [00:52:56] 学校买了pr [00:52:58] 反正那个pr我记得pr的那个那个就是Adobe [00:53:02] 全家桶里面那些打开好像是他自己的菜单 [00:53:05] 因为它可能会提供一些预览啊这样的功能 [00:53:08] 然后在那个菜单 [00:53:09] 你说它好用吧 [00:53:10] 它好用的时候挺好用 [00:53:11] 说哪难用吧 [00:53:12] 它不符符合你使用习惯的时候很难用 [00:53:15] 然后如果你是一个操作系统的这个打开文件 [00:53:18] 文档文件窗口 [00:53:19] 那也不是那么的好用 [00:53:23] 虽然它集成了绝大部分常用的功能 [00:53:25] 但UNIX里面这个答案不一样 [00:53:27] 比如说我现在想要为我的 [00:53:31] 想要为我的任何一个东西 [00:53:33] 比如说我现在用Vim或者用Emacs [00:53:35] 我们互相圣战 [00:53:36] 你用任何一个都可以 [00:53:37] 或者用NANO [00:53:38] 我有没有NANO [00:53:39] 有好我有Neovim [00:53:41] 这个我想给他配一个菜单 [00:53:44] 我就可以配一个我自己的菜单 [00:53:47] 然后就得到了一个 [00:53:49] 你看这是一个打开文件的菜单 [00:53:51] 这就是工具的组合性 [00:53:52] 它这个模块 [00:53:56] 因为UNIX当然这是一个偶然 [00:53:58] 可能在系统的设计者也没有想到那么多 [00:54:02] 那么多事情 [00:54:04] 但比如说我就可以 [00:54:06] 随便啊随便找一个 [00:54:08] 比如说第一课的index [00:54:10] 我就用NANO打开它了 [00:54:13] 所以你看到share这个share的文本接口是和编程语言 [00:54:18] 还有UNIX的管道文本接口 [00:54:20] 它是一脉相承的 [00:54:22] 把它放在一起 [00:54:23] 你就有一个无限组合和复用的空间 [00:54:26] 允许你写程序 [00:54:27] 所以我上操作系统课的时候 [00:54:28] 我经常说你们都是都是程序员 [00:54:33] 你们在用Linux用UNIX的那一刻起 [00:54:36] 你每个人都是程序员 [00:54:37] 因为你做的每一个命令它都是一个小程序 [00:54:39] 而这个小程序就是负责把各种各样的 [00:54:42] do one thing and do well的工具给组合起来 [00:54:45] 这种可组合性是非常关键的 [00:54:48] 就到今天 [00:54:49] 你都很难在一个GUI程序里面 [00:54:52] 得到这样的组合性 [00:54:53] 那我觉得这样的ground breaking东西也是可以 [00:54:55] 应该是可以做得出来的 [00:54:58] 可以畅想一下 [00:54:59] 这是unique UNIX [00:55:01] 然后第二个也是大家熟悉的例子 [00:55:04] 其实我已经讲过讲过几次了 [00:55:05] 就是关系数据库 [00:55:07] A relation model of data is [00:55:09] A basis for protecting user from [00:55:11] Formatted data system from the potentially disruptive changing data [00:55:15] Representation [00:55:15] Caused by growth in the data bank and changes in traffic [00:55:18] 也就是说如果你用文件系统去管理数据 [00:55:22] 你就面对一个row interface [00:55:25] 它的好处是UNIX世界里面的工具可以复用 [00:55:29] 它的坏处是你太过自由 [00:55:32] 以至于啊他没有这种统一的统一的管理 [00:55:36] 然后这个关系代数 [00:55:38] 他做了一个非常了不起的 [00:55:41] 当然我觉得就是因为我 [00:55:43] 我自己做过一段时间的程序合成 [00:55:44] 然后发现我一但凡 [00:55:47] 我要做这个和人类世界相关的这些程序合成 [00:55:53] 我们绕不开的就是E. F. Codd的 [00:55:55] 1970年的这个论文 [00:55:56] 它其实是对世界建立一个模型 [00:55:59] 他说我们的世界里面的数据 [00:56:02] 可以用这个二维表 [00:56:05] 我相相信大家应该是熟悉的 [00:56:09] 就有好多好多的表和表和表之间的 [00:56:12] 一些操作来表示 [00:56:14] 然后然后你几乎可以用这个表和表之间的 [00:56:18] 比如说drawing这样的操作来 [00:56:21] 处理任何你所需要的数据管理 [00:56:24] 而这个智人这个数据怎么管怎么存 [00:56:26] 它可靠不可靠 [00:56:27] 全部交给底下的数据库系统的 [00:56:30] 实现也就是说在这个关系代数的基础上 [00:56:33] 我们有了SQL [00:56:34] 结构化的查询语言 [00:56:35] 这个这个语言你看也是一个语言 [00:56:38] 上面UNIX share bash也是一个语言 [00:56:41] 然后这个SQL也是一个语言 [00:56:43] 所以当你grow一个东西的时候 [00:56:47] 一个东西可以grow的时候 [00:56:48] 它多多少少都会带一个语义的性质 [00:56:51] 它是一个接口 [00:56:51] 然后这个接口它足够的灵活 [00:56:53] 所以它就会像一个编程编程语言 [00:56:56] SQL有了SQL [00:56:57] 然后有了查询优化器 [00:56:58] 然后有了事物以后 [00:57:00] 那就数据库在任何时候 [00:57:03] 这个二维表都是当前世界的一个状态 [00:57:07] 一个状态 [00:57:08] 然后这个状态经过一次数据库的transaction [00:57:12] 会到下一个状态 [00:57:14] 然后这个数据库还可以有视图 [00:57:16] 比如说View [00:57:17] 我当前的状态可以用一个查询来创建一个 [00:57:22] 我对这个世界 [00:57:23] 当前世界的理解 [00:57:24] 而且数据库系统的实现 [00:57:27] 给了我们可以近乎无限scale的能力 [00:57:30] 到今天我们都可以SQL的额数据依然可以 [00:57:35] 无限的无无线的啊scale [00:57:39] 然后我们就可以用这样的一个信息系统 [00:57:42] 所以数据库其实是带起了整个人类世界的数据 [00:57:46] 信息化的 [00:57:47] 一波很大的浪潮 [00:57:49] 你想图书馆里的书可以信息化 [00:57:51] 学籍管理的系统可以信息化 [00:57:52] 你的财务的信息可以信息化 [00:57:55] 在线商城的订单可以信息化 [00:57:57] 所有东西都可以用这个relational database来表示 [00:58:01] 它是逻辑关系 [00:58:02] 它表示了实体和实体之间的啊逻辑关系 [00:58:06] 那只要把这个逻辑关系理清楚 [00:58:08] 你就可以在现实世界里面实现信息系统啧 [00:58:12] 这也是一个非常顶级的 [00:58:14] 隔离了复杂性的啊抽象 [00:58:16] 而且它有无穷的扩展能力 [00:58:19] 它只规定说我这个世界里面有表database的schema [00:58:22] 但这个schema是什么 [00:58:23] 可以由你来扩展 [00:58:24] 由你来决定 [00:58:25] 为这个应用来定制 [00:58:29] 然后第三个案例就是Internet [00:58:31] Internet也是一个非常非常典型的案例 [00:58:34] 它解决一个非常简单的问题 [00:58:36] 我今天为什么可以站在那上课 [00:58:39] 上课的时候 [00:58:40] 我同时还在推推流 [00:58:42] 然后我推流的时候 [00:58:43] 我的手机同时还能兼看我推流的结果和弹幕 [00:58:49] 他把整个世界给连起来了 [00:58:51] 然后这个连起来它也是一个可以成长的架构 [00:58:56] 他希望的是我要做一个架构 [00:58:58] 它 [00:58:58] 他负责他能够解决这个系统里面一部分的问题 [00:59:01] 比如说你当我用TCP的时候 [00:59:04] 你再也不用担心这个数据传输到底可靠不可靠 [00:59:08] 我无论什么样的数据流 [00:59:10] 都可以稳定可靠的 [00:59:11] 从一个点送到另外一个点 [00:59:13] 从我的手机上和另外世界上 [00:59:16] 另外一头的某一个点连起来都是没有问题的 [00:59:19] 然后再至于上面跑什么样的应用程序 [00:59:23] 跑什么应用程序 [00:59:25] 我能够支持什么就支持什么 [00:59:28] 不要做预设 [00:59:29] 所以你可以读一下这个 [00:59:31] 我觉得还是啊这个也是非常啊非常非常经典 [00:59:40] RFC [00:59:42] 1958 [00:59:45] 啊RFC 1958architecture principle of the Internet [00:59:50] 1996年6月 [00:59:51] 架构的原则 [00:59:57] 唯一应该永存的原则就是变化本身 [01:00:01] 这个原则就说它要承载这个世界的变化 [01:00:06] 所以看他就做一件事情 [01:00:08] 网络networking [01:00:09] 他要做一件事情 [01:00:11] 目标就是connectivity [01:00:13] 然后在connectivity的基础上 [01:00:16] 上面就会有很多的这个应用可以爆发出来 [01:00:20] 但是他不管 [01:00:21] 甚至比如说现现在有些数据中心甚至还在用 [01:00:24] 还在用TCPIP [01:00:25] 虽然现在有更好的 [01:00:27] 如果你要真的要在 [01:00:28] 比如说要在GPU之间传输数据 [01:00:30] 那可能需要更好的 [01:00:32] 协议网络只管高校传datagram [01:00:36] 但是这个传的时候还有很多细节 [01:00:38] 比如说像TCP点有这种拥塞控制这种难题 [01:00:41] 然后还有这种断联连接的管理 [01:00:45] 连接的中断什么时候就决定 [01:00:46] 这个连接就中断了 [01:00:48] 而这些是非常重要的 [01:00:50] 它架构里面需要包含的设计 [01:00:54] 然后其他的基本上就全部都这个交给别人好 [01:00:59] 那在这个基础上 [01:01:01] 他做好了这个connectivity这一件事 [01:01:04] 上面的世界其实变了很多 [01:01:06] 早在早期的时候 [01:01:08] 我跟大家说过 [01:01:09] 我读书的时候整还没有 [01:01:11] 现在这个全网HTTPS [01:01:13] 那我们都是在HTTP上裸奔的 [01:01:15] 然后在HTTP 上裸奔的时候 [01:01:17] 我们都是直接用telnet去连到南京大学 [01:01:19] 小百合BBS去灌水的 [01:01:22] 但今天你看tel net [01:01:23] 那显然是已经 [01:01:26] 安全性等都达不到我们要的要求 [01:01:28] 我们有SSH [01:01:30] 然后在我那个时候传输文件是唉 [01:01:32] 用的FTP FTP [01:01:34] 为什么好 [01:01:35] FTP是因为它协议里面本身就支持断点续传 [01:01:39] 所以那个时候我们IP一下电影 [01:01:41] 在校内里面下电影下下的非常开心 [01:01:43] 我因为我下那个时候网络可能不是很稳定 [01:01:46] 或者反正经常有问题 [01:01:48] 或者我下到一半的时候 [01:01:49] 我可能就要诶我要停下来 [01:01:51] 但我下一次我可以从这个断点继续继续传说 [01:01:55] 在协议里面支持了 [01:01:56] 但今天它就显得也是因为没有安全啊等 [01:02:00] 虽虽然可能有安全的扩展 [01:02:01] 但是今天做软件变容易了 [01:02:04] 所以我们可以把这些功能全部实现在 [01:02:06] HTTPS的一个接口里 [01:02:07] 所以HTTPS又变成了一个 [01:02:10] 有点像这个TCPIP的上一层 [01:02:13] 今天你访问所有的这个micro services [01:02:16] 你的所有的 [01:02:18] 你只要出了你的本地的东西 [01:02:20] 和Internet上面的另外一个服务商 [01:02:22] 全都是HTTPS [01:02:24] 然后他有这个restful API的这样的一个规范 [01:02:29] 当然是建议的 [01:02:30] 你可以不做 [01:02:31] 你可以不严格 [01:02:32] 严严格遵循 [01:02:33] 它在这个体系上面又长出了很多的东西 [01:02:37] 在共同的规则之上持续的啊更换局部 [01:02:43] 所以所以就好像是他建设了一个 [01:02:46] 城市的交通网络 [01:02:48] 然后这个建筑它就一直都在都在 [01:02:52] 不断的向前向前演进 [01:02:54] 这几个是其实是大家比较熟悉的例子 [01:02:56] 你们会在 [01:02:56] 就为什么要在你们学大学里面学这些课程 [01:03:00] 当然你肯定是要学一些具体的技术 [01:03:02] 比如说你有一天你真的会出去写SQL [01:03:06] 真真的有一天你拖网的时候 [01:03:08] 你真的需要一个SQL的时候 [01:03:09] 你可能你还得你还得自己手写一下 [01:03:13] 你得有概念对着手才能能写出来 [01:03:16] 但另一个方面 [01:03:16] 你更多的是看到这些沉淀下来的经典 [01:03:20] 它为什么能成为经典 [01:03:22] 这些系统当然我讲的是 [01:03:24] 可能它偏软件架构的 [01:03:26] 这方面他是怎么样应对变化 [01:03:28] 你可以体会为什么这样的一个好的设计 [01:03:31] 它可以支撑起一个生态应对 [01:03:34] 这些这些变化 [01:03:36] 当然我们还是要讲软件工程的 [01:03:39] 这是生成式软件工程 [01:03:40] 这个我们还是要讲软件的架构的 [01:03:45] 我们在在刚才的就是这种systems里面 [01:03:50] 看到了一些 [01:03:51] 因为system是最 [01:03:53] 他是为了提供支撑应用的通用抽象层 [01:03:56] 所以它会特它抽象 [01:03:58] 一般要做的特别好 [01:03:59] 才能在很长的演化里面这个存活下来 [01:04:02] 从IBM360360是一个非常经典的系统 [01:04:05] 然后到UNIX [01:04:06] 面向对象的编程语言 [01:04:08] Internet Android [01:04:09] Android我应该会专门有一次课来讲 [01:04:12] 然后到比如说云原生里面的这种这种 [01:04:15] Kubernetes [01:04:16] 那我们有没有面向于客户需求的软件架构 [01:04:22] 对吧软件架构呢当 [01:04:26] 系统这一层都打好了 [01:04:27] 地基打好了以后 [01:04:29] 我们有了操作系统 [01:04:30] 在操作系统上面有了数据库 [01:04:32] 然后在操作系统上面有了Internet [01:04:34] 那这个舞台其实就已经为人类搭建好了 [01:04:37] 各种各样的业务逻辑系统 [01:04:39] 教务系统 [01:04:40] 图书管理系统就全部都可以上线了 [01:04:43] 那我们在这个已经打好的抽象层的基础上 [01:04:48] 我们能做什么 [01:04:49] 很自然的想法 [01:04:51] 如果你 [01:04:52] 你今天想 [01:04:53] 你回到1998年或者回到2000年 [01:04:56] 你要设计为 [01:04:58] 我们的学校或者为企业设计设计软件系统 [01:05:02] 你手上有的英特尔瓦数据库操作系统 [01:05:06] 你最直接的想法就是 [01:05:07] 你为每一个页面 [01:05:09] 或者为每一个功能 [01:05:11] 都做成是一个C口的transaction [01:05:14] 因为数据库已经完成了物理世界 [01:05:20] 过程在信息世界里面的投影 [01:05:23] 他就是一个好多表表格的这样的一个啊分解 [01:05:28] 那我们刚才说是由数据库的事务驱动 [01:05:32] 这个状态的变化 [01:05:33] 而我们可以把需求 [01:05:38] 这时候你就能对上了 [01:05:39] 你就可以可以理解为什么那些软件工程的书 [01:05:43] 你看不明白 [01:05:44] 那些软件工程的书 [01:05:44] 他要他要这样写 [01:05:45] 他要把这个需求软件工程做的事 [01:05:49] 或者说如果你要构建业务系统做的事 [01:05:51] 就是把需求投射成不同的 [01:05:54] 也就是一个分解 [01:05:57] 不同的这个需求点 [01:06:00] 然后每个需求点变成一个数据库的transaction [01:06:03] 然后以及我可以为这个当前的状态 [01:06:07] 提供各种各样的数据库的视图 [01:06:09] 数据库视图也是一个查询 [01:06:10] 比如说我想要 [01:06:12] 看有哪些学生选择了我的 [01:06:15] 这一门生成式软件工程课程 [01:06:17] 那我就会写一个select [01:06:19] 这样把这个数据库的试图 [01:06:23] 在某种程度上 [01:06:24] 你可以理解成是一个一个切片 [01:06:27] 拿出来 [01:06:27] 然后这个切片就可以显示在某一个 [01:06:30] 某一个地方 [01:06:32] 这样我们就得到了一个啊最最经典的 [01:06:37] 应用程序的架构 [01:06:39] 然后这个架构一定程度上其实是可以 [01:06:45] 就在这个需在在这个需求点相对 [01:06:50] 就每一个需求点都是一个数据库 [01:06:52] 事务查询的时候 [01:06:53] 这个架构其实是非常好的 [01:06:55] 这架构其实是非常好的 [01:06:56] 然后直接有一波应用的浪潮在 [01:07:00] 包括这有好几个因素 [01:07:02] 包括这个操作系统 [01:07:05] CRUD数据库和Internet [01:07:06] 以及啊刚才我讲到那个OOPSLA'98的keynote里面 [01:07:10] 他讲Java语言的设计 [01:07:12] 那个时候1998年Java如日中天的时候 [01:07:16] Java在允许我们直接在HTML里面写代码 [01:07:23] 当然这个不是一个那个很新的想法 [01:07:26] 比如今天我们你们你们如果要对 [01:07:29] 这也是一个很好的架构 [01:07:31] 叫模板语言 [01:07:33] 你们你们有没有过自己写一个AI应用 [01:07:36] 然后你需要自己定制一个prompt [01:07:40] 我不知道你们有没有过这个需求 [01:07:41] 然后你们怎么拼prompt [01:07:44] 如果你们想要 [01:07:45] 你想要prompt [01:07:46] 你们会怎么样 [01:07:47] 是prompt等于A加B加C这样拼 [01:07:53] 你可以这样拼 [01:07:54] 然后加上一个哎sorry [01:08:00] 写好多好多好多好多prompt [01:08:01] 然后再加上什么D [01:08:05] 再加上你们会这样写prompt吗 [01:08:10] 一个更好的办法是用一个模板语言 [01:08:13] 比如说你把prompt说写一个you are [01:08:17] 什么什么什么 [01:08:19] 然后比如说你的instructions [01:08:25] 哎你用一个又是一个语言 [01:08:28] 这是一个可扩展的语言 [01:08:29] 然后这个语言对它有一个语义 [01:08:32] 是你可以在渲染这个模板的时候 [01:08:34] 给他传一个叫instructions的变量 [01:08:36] 然后他会把这个字符串就直接贴在这 [01:08:38] 这样你的prompt就变得好维护了 [01:08:41] 如果你有一个Python表达式 [01:08:42] 你就你就你就很难维护掉 [01:08:43] 比如说你有一个用Python环境 [01:08:45] 一个gr to的模板 [01:08:47] 你可以把很多东西 [01:08:49] 比如说今天的日期 [01:08:51] 你可能需要今天的日期 [01:08:53] 你就可以把日期传入传入传传入这里 [01:08:56] 然后这个feature [01:08:58] 最早大概你看在PHP / ASP的时候 [01:09:00] 就已经探索怎么样在 [01:09:03] 页面里面嵌入服务端的脚本了 [01:09:05] 然后在这个Java时代 [01:09:09] Java时代它有 [01:09:11] 对它有它有几种不同的写法 [01:09:13] 早期的时候是这样写 [01:09:15] 很像吧 [01:09:16] 一个一个这个 [01:09:17] 它可以把Java代码和HTML模板嵌在一起 [01:09:21] 牵在一起以后 [01:09:22] 甚至你看这只可以一起循环 [01:09:25] 这个像像 [01:09:26] 比如你今天看Jinja里面也有这样的功能 [01:09:30] 有了这样的功能以后 [01:09:33] 我们开发就是开发这种以数据库为中心的应用 [01:09:38] 就变得前所未有的容易了 [01:09:40] 我只需要我的每一个数据库应用 [01:09:43] 尤其是一个页面 [01:09:44] 我只需要一个database transaction [01:09:46] 把所有的是我需要的数据都捞出来捞出来 [01:09:50] 比如说存放到一些list里 [01:09:52] 存放到一些map里 [01:09:53] 然后他把map就直接用这种方式 [01:09:56] 投射成一个HTML的页面 [01:09:59] 而且HTML里面还有一个很好的设计 [01:10:01] 它把这个DOM的数和样式给分开了 [01:10:08] CSS和HTML [01:10:09] HTML不管样式 [01:10:11] 但是我可以通过给不同的这个element [01:10:16] 不同的样式 [01:10:17] 我可以排除几乎任何形式的界面 [01:10:21] 这样信息系统就可以做了 [01:10:22] 我可以在这放 [01:10:23] 我在可以在这放button [01:10:25] 我在可可以在这放 [01:10:26] 任何代码生成的动态的啊东西 [01:10:30] 这样你就可以看到为什么 [01:10:34] 像图书管理系统在那个时候就集中式的 [01:10:37] 爆发了 [01:10:38] 然后在这里面就有了 [01:10:40] 这其实其实是这个想法就在 [01:10:44] 每个页面都是一个SQL transaction [01:10:49] 导出了一个非常重要的模式 [01:10:53] 叫做MVC model vs controllers [01:10:56] 我们也读一下 [01:11:04] 对他就是一个非常小的 [01:11:12] 是一个一个两页纸的 [01:11:16] 这个也是1972年哎 [01:11:20] 1979年1979年 [01:11:23] models represent knowledge [01:11:25] 就是一个single point of [01:11:28] 当前对当前世界的knowledge [01:11:31] 谁选了什么课 [01:11:32] 有哪些订单 [01:11:33] 然后 View 是一个 visual representation of its model [01:11:37] 所以所谓的 View就是我要看到的View 是一个 function of [01:11:44] 然后这个function是个pure function [01:11:46] 所以你不希望这个View能够有任何改变 [01:11:50] 这个的能力 [01:11:50] 如果你希望你 [01:11:53] 如果你希望有一个操作可以改变这个世界 [01:11:56] 你就需要一个controller [01:11:58] A controller is the link between a user and the system [01:12:00] It provides user is input by a drain for relevant [01:12:03] Wheels to present in property places of spring [01:12:05] 然后output by prison user with manuals [01:12:08] Are the meanings of giving commands and data [01:12:10] 所以他就把这个就是把需求的能修改的点翻 [01:12:15] 翻译成transaction [01:12:16] 然后把你看到的东西变成状态的一个投射 [01:12:20] 非常干净的一个一个架构 [01:12:23] 然后像也很自然 [01:12:24] 它会成为任何信息系统的一个标准的架构 [01:12:27] 比如说如果你要实现一个教务系统的话 [01:12:29] model就是这个课程选课记录啊各种 [01:12:33] 然后 View 就是你看到的那个页面 [01:12:35] 然后control就是你那个各种按钮点下来 [01:12:38] 或者那个输入框点下来 [01:12:40] 它相应的会得进入一个一个这个 [01:12:43] 数据库的事务好 [01:12:47] 这是大家熟悉的 [01:12:50] 那这有什么问题 [01:12:55] 这有什么问题 [01:12:57] 问题是你如果啊如果是在HTML单页面的模式 [01:13:03] 就只有一个HTML页面的时候 [01:13:06] 然后每次你点一个按钮以后 [01:13:08] 这个页面都会重新刷新 [01:13:10] 重新更新的时候 [01:13:11] 那这个其实是成立的 [01:13:13] 但是在今天这个我们的界面上有大量的元素 [01:13:19] 他们的状态是不在这个model里面的 [01:13:23] 比如说我们可以展开 [01:13:25] 随时可以展开一个菜单 [01:13:27] 展开一个菜单 [01:13:28] 或者我有个搜索框 [01:13:30] 你们都用过这个这个功能吧 [01:13:32] 我现在有一个视图 [01:13:34] 有一个View [01:13:35] 然后这个 View [01:13:36] 比如说是学生的列表 [01:13:38] 然后这个学生的列表一定是有一个搜索框 [01:13:41] 比如说我要找哪一个名字的同学 [01:13:43] 找一个这个对找或者找一个学号 [01:13:48] 然后教务系统讨厌的地方 [01:13:50] 就是搜索被做成了一个这个某种 [01:13:55] 一个一个这个一个database transaction [01:13:58] 我需要在搜索框里面先输入 [01:14:00] 然后输入完了以后点搜索 [01:14:02] 然后他把这个搜索结果结果返回给我 [01:14:05] 然后有的时候这种 [01:14:08] 他已经把这个列表给我了以后 [01:14:09] 我只需要一个过滤器 [01:14:11] 然后这个过滤器不涉及到任何的 [01:14:13] model的状态的改变 [01:14:14] 也不会去再去查这个model [01:14:16] model已经已经试图已经在视图里了 [01:14:19] 但我仍然需要对视图的状态做改变 [01:14:21] 有些我临时的修改我hover的那些element [01:14:24] 我的我的鼠标移过一个元素的时候 [01:14:27] 它应该亮起来这些 [01:14:28] 然后这些它其实也需要一个model [01:14:33] 但这个model不应该存在数据库里 [01:14:35] 所以 [01:14:37] 为什么不把ViewState也当成是一个model [01:14:41] 然后你就发明了这个MVVM [01:14:45] 就现在比较时髦的 [01:14:47] 就最近比较时髦的一个 [01:14:49] 一个一个一个概念 [01:14:53] 我看我有没有时间 [01:14:55] OK我还有一点时间 [01:14:57] 我们有两个这个和MVVM有关系的 [01:15:01] 这个设计 [01:15:01] 其实我觉得我自己 [01:15:03] 我不引起圣战 [01:15:05] 我自己更偏向于这个啊这个React [01:15:08] 因为React有一个就View [01:15:11] 对开发者来说是比较比较直直观的 [01:15:14] 就如果你写过一点前端 [01:15:16] 你立即就可以上手这个这个View [01:15:19] 然后React来说它更纯净纯粹一点 [01:15:22] 他希望的是保留这个模型 [01:15:25] 保留我们我们的View [01:15:31] 也就是说我们看到的东西 [01:15:33] 网页上面看到的东西是整个数据库的一个投射 [01:15:36] 然后这个是 View,上面还可以有一个状态 [01:15:39] 比如说我是不是hover了 [01:15:42] 有没有某一个东西被 hover 了 [01:15:44] 然后我的query string [01:15:47] query string是什么 [01:15:49] 它可以有一些自己的页面上面的状态 [01:15:53] 然后这个状态随时可以改 [01:15:55] 我有一个我 [01:15:56] 我这个query string是映射到一个文本框的 [01:15:58] 然后这个文本框和这个query stream [01:16:00] 的状态是绑定的 [01:16:01] View 里当然也可以绑定这个状态 [01:16:03] 然后这个hover也是的 [01:16:05] 它每一个每个element [01:16:06] 它可能都有一个这个是否hover的 [01:16:09] hover的状态 [01:16:10] 然后他想说好 [01:16:12] 那我能不能就写一个函数 [01:16:14] 说我的DOM tree [01:16:17] 我把这个数据库擦掉了 [01:16:20] 我的DOM tree就等于function of [01:16:24] 这个你能不能就直接写定义这个函数 [01:16:30] 我定义一个pure方程 [01:16:31] 一个没有任何副作用的函数 [01:16:33] 我输入这个 [01:16:36] 我输入当前的所有的界面上面的状态 [01:16:41] 然后我就可以把这个DOM tree给算出来 [01:16:44] 这肯定可以 [01:16:46] 可以是可以 [01:16:47] 但这里有一个非常严重的问题是性能 [01:16:51] 你的网页上面可以能有几万个元素 [01:16:55] 然后你当当你的这个query string改变的时候 [01:17:00] 开query string [01:17:01] 当你当你这里你 [01:17:04] 你进入了一个字符的时候 [01:17:08] 那整个这个状态全部都要重算 [01:17:11] 所以他就在这个系统里面做了大量的这种 [01:17:14] virtual DOM的这个工作来把这些复杂性给藏起来 [01:17:18] 你只要按照我的模型来声明什么时候 [01:17:22] 状态是什么 [01:17:24] 状态是怎么变的 [01:17:26] 剩下你就全部都不要管他帮你更新 [01:17:30] 然后VIVO更像是 [01:17:35] 他帮你组建了一个依赖关系图 [01:17:38] 然后你写的时候 [01:17:40] 当你写的时候 [01:17:40] 你写这样的 [01:17:42] input等于什么的时候 [01:17:43] 他就会建立一个依赖关系 [01:17:45] 然后帮你保持这个同步 [01:17:48] 其实其实两个差不多 [01:17:50] 两个差不多 [01:17:52] 而且随着这个 [01:17:56] 随着这个好像VIVO新版本的发布 [01:17:59] 他们好像变得更像了 [01:18:01] 但是我们用的比较少 [01:18:03] 所以我没有这个没有这个发言权 [01:18:05] 但你看到在这里就这两个框架 [01:18:08] 它设计的思路都需要你隔离复杂性 [01:18:14] 你希望的是 [01:18:15] 你可以把有些变的东西和不变的东西 [01:18:19] 要分分离开来 [01:18:20] 分离开来 [01:18:21] 然后把逻辑和数据的源头要分离开来 [01:18:23] 这样你可以分开管理你 [01:18:25] 因为我们是small head [01:18:27] 我们头很小 [01:18:29] 那我们如果关注状态的时候 [01:18:32] 最好就不要关注这个状态是怎么变成DOM的 [01:18:35] 然后在我关注DOM [01:18:37] 当我状态完全确定以后 [01:18:39] 我再看怎么把这个状态翻译成我的DOM [01:18:44] 这样啊都是由这两个都是由状态决定 [01:18:49] 界面都是一个啊pure pure function [01:18:52] 但是更新的模型稍微啊有一点有一点不一样 [01:18:58] 这是你们现在可能比如说要做一些前端开发 [01:19:00] 会比较熟悉的 [01:19:01] 但是但是但是啊MVC / MVVM [01:19:07] 这个他们没有解决真正的essential complexity [01:19:13] 尤其是面向长流程 [01:19:17] 真正的长流程的任务 [01:19:19] 比如说你假设你要实现一个淘宝 [01:19:25] 当然你不会去实现这么复杂的东西 [01:19:27] 淘宝的麻烦在哪里 [01:19:30] 你说下单好 [01:19:31] 下单没问题 [01:19:32] 然后下单以后他的流程之后会涉及到 [01:19:37] 比如说下单以后 [01:19:38] 你库存要锁定 [01:19:39] 锁定完了以后你要计算优惠 [01:19:41] 然后计算优惠有你要支付 [01:19:43] 然后注意这个支付是需要用户确认的 [01:19:46] 然后支付完了以后你会风控发货退款 [01:19:48] 售后对账 [01:19:49] 所以这个业务流程你用一个状态 [01:19:54] 就刚才我们做了一个 [01:19:56] 其实做了一个不切实际的假设 [01:20:01] 就是我们用当前状态 [01:20:06] 来表示这个事件 [01:20:08] 那当然OK没问题 [01:20:09] 可以用二维表来表示 [01:20:10] 那我在当前状态下 [01:20:14] 我怎么表示我这个订单到底进行到哪一步了 [01:20:21] 你们最直接的方法就是在这个数据库里面 [01:20:24] 放一张表叫order [01:20:27] 然后order里面这个order [01:20:29] 这个表里面有一列叫做status [01:20:33] 然后里面有一个有一再 [01:20:37] 比如有一个一就代表是在这个下单 [01:20:43] 如果是一是下单 [01:20:44] 二是库存 [01:20:48] 三是什么什么什么 [01:20:50] 或者你可以把它展开来 [01:20:53] order你也可以order展开来 [01:20:54] 比如说是不是不是下单库存支付 [01:21:00] 有没有支付 [01:21:01] 然后比如说他有一个go go go go go好 [01:21:05] 然后这个时候这个状态机就会变得非常复杂了 [01:21:08] 而且你 [01:21:11] 你可能遇到的问题是在某一个节点上 [01:21:16] 比如说触发了风控以后 [01:21:18] 你需要设计它返回的逻辑 [01:21:20] 这是一个essential complexity [01:21:22] 这是就这是这个业务系统里面的逻辑 [01:21:26] 然后随着这个业务系统越长越大 [01:21:30] 比如不管是这样的 [01:21:33] 管理订单的系统 [01:21:35] 还是比如说教务系统里面的一个学生 [01:21:37] 你会发生发现什么 [01:21:40] 你会发现有的学生处于既可以毕业 [01:21:44] 又不能毕业的状态 [01:21:45] 因为他hack的时候 [01:21:47] 就有的时候你总是在某一个流程上面 [01:21:49] 比如程序有bug [01:21:50] 你的程序有一个bug [01:21:51] 你的程序也可能是 [01:21:54] 就是就是有一个特批 [01:21:56] 导致某一个地方被override了 [01:21:58] 本来这个地方应该风控应该触发的 [01:22:00] 但是其实没有问题的 [01:22:02] 然后这个风控他就由另外一个人override了 [01:22:05] 然后在这个系统里面 [01:22:06] 因为有错综复杂的逻辑 [01:22:07] 最后就会变成这个处于一个风控失败的状态 [01:22:12] 但是他进入了下一个下一个环节 [01:22:15] 那这个数据到底是什么 [01:22:16] 你怎么解读 [01:22:19] 这种不一致的数据 [01:22:21] 它甚至有的时候你的数据都是silently corrupt的 [01:22:24] 我相信我们学校的教务系统里面有无穷多这样 [01:22:27] 因为经常手操工程师 [01:22:29] 手操教学里面有很多这种就根本就不对 [01:22:32] 不一致的数据在里面 [01:22:33] 反正好像最后没有影响这个同学毕业 [01:22:36] 然后他也就只能啊只能不管了 [01:22:39] 这是因为我们的业务流程 [01:22:42] 它可能是一整条很长的流程 [01:22:45] 它既要编码 [01:22:47] 过过去又要编码 [01:22:49] 现在又要编码 [01:22:50] 未来 [01:22:51] 所以本质上这是现在的状态的一个快照 [01:22:54] 但这个快照是由过去推演过来的 [01:22:57] 它怎么过来的 [01:22:58] 这个丢了丢了以后 [01:23:00] 你这个一不一致 [01:23:03] 其实就会就会比较复杂 [01:23:05] 然后我先举一个更简单一点的例子 [01:23:08] 你们有没有同学记过账 [01:23:12] 就比如说支付宝会给你一个记账 [01:23:14] 但如果如果你如果你想要真正严肃的记账 [01:23:22] 他会额会计实际上是有一个要求的 [01:23:25] 叫做复式记账法 [01:23:26] 那我们也可以 [01:23:33] 他是为了防错 [01:23:34] 就是他就是为了防止这种数据库不一致 [01:23:38] 这种不一致 [01:23:39] 就当你发生不一致的时候 [01:23:40] 他能够检查出来 [01:23:42] 这个 [01:23:43] 他要求每一笔交易都同时计入 [01:23:45] 两个或者以上的账户 [01:23:48] 且金额相等 [01:23:50] 方向相反 [01:23:53] 你看他说只记一条 [01:23:58] 简单但是无法反映业务的完整结构 [01:24:01] 也不具备自平衡的校验校验能力 [01:24:05] 你看他可以试算试算平衡 [01:24:07] 它跟就人类发明这种东西 [01:24:10] 就是因为就是因为一个当前状态 [01:24:14] 他很有可能是corrupted [01:24:16] 是不对的 [01:24:17] 那他要把这个为什么不对 [01:24:19] 一定程度上要给 [01:24:23] 要给要给找出来 [01:24:28] 对然后他有一个啊你们不需要知道 [01:24:31] 你们不需要知道这是什么 [01:24:33] 我也不知道它是什么 [01:24:34] 但是我有这个记账的需求 [01:24:37] 然后我有这个记账需求的时候 [01:24:39] 那我当然可以 [01:24:41] 就用这个信息系统的方式来 [01:24:46] 在做一张一个叫资产负债表的东西 [01:24:51] 比如说我可以介绍一下 [01:24:57] 比如说我现在就有一个需求 [01:24:59] 我就是要记一个资产负债表 [01:25:03] 然后这个资产负债表大概就是长这样的 [01:25:05] 有什么现金存货 [01:25:07] 固定资产 [01:25:08] 应付账款 [01:25:09] 短期借款 [01:25:10] 所有者权益等 [01:25:11] 然后这个是相当于 [01:25:13] 如果你要以和符合这个会计法规的方式记账 [01:25:17] 就必须要得到一张这样的表 [01:25:19] 但是我对会计没有任何的知识 [01:25:21] 那我要怎么样设计一个软件的架构 [01:25:24] 能够帮我 [01:25:26] 在在我不知道就没有任何知识的情况下 [01:25:30] 设计一个好的信息系统 [01:25:31] 这时候我就用了一般sourcing [01:25:33] 我就用了event sourcing [01:25:34] 我干了这样的一件事 [01:25:37] 我不再是记录当前的状态 [01:25:42] 而是 [01:25:46] 我记录所有状态的迁移 [01:25:49] 就说我记录一个数据结构有很多办法吗 [01:25:51] 我可以记录这个数据结构 [01:25:52] 当前的状态 [01:25:53] 我也可以记录这个数据结构是怎么怎么来的 [01:25:56] 所以我其实就是一笔流水账 [01:26:01] 刘刘刘水账 [01:26:02] 比如说这个今天是收了1000块钱 [01:26:05] 然后明天又减了1000块钱 [01:26:08] 然后是为什么 [01:26:13] 好但我不会记账 [01:26:15] 我没有设计那个数据库系统 [01:26:18] 没有设置那个数据库 [01:26:19] 而我设计的是我直接让AI设计了一个 [01:26:26] 这样的东西我设计了一个小的 [01:26:30] 像编译器一样的东西 [01:26:32] 然后我有很多的记账的动作 [01:26:35] 就是我就实际这是在实际中 [01:26:38] 从那个最终需要交付的那个资产负债表里面 [01:26:43] 所有可能的条目里面提取出来的 [01:26:45] 然后那么比如说我要 [01:26:47] 我要我要付钱 [01:26:49] 比如说我要付一个营业成本成本的钱 [01:26:51] 那每付出一份我都会在这些表格上面 [01:26:55] 比如说货币资金会怎么样 [01:26:57] 所有者权益会怎么样 [01:26:58] 这个应付账款会怎么样 [01:27:00] 然后这个然后在现金流量表上会发生什么 [01:27:05] 然后你看到这个这个表格完全是AI生成的 [01:27:08] 我就选择选择相信AI [01:27:11] 选选择相信AI [01:27:13] 以及相信最终那个那最终那个校验会找到这个 [01:27:17] 这里面是不是会有任何的啊问题 [01:27:20] 然后接下来我做了一个什么样的架构设计 [01:27:29] 我做了一个什么样的架构设计 [01:27:31] 我用LM做编译器 [01:27:33] 就这是一个这是一个persistent history [01:27:41] 就是不可篡改的 [01:27:43] 就发生了什么 [01:27:44] 比如说我今天我要 [01:27:46] 而我要允许某一个不能毕业的同学毕业 [01:27:48] 我也必须要在这里面写一个persistence [01:27:51] 就是说我要允许什么什么什么毕业 [01:27:54] 然后这样的一个流水账的条目 [01:27:57] 会用LLM编译成 [01:28:00] 刚才你们看到的那一个小的操 [01:28:04] 好多小的操作 [01:28:08] 然后当前的状态 [01:28:14] 是由所有的这个操作reduce得到的 [01:28:18] 就相当于是每一个操作都会说 [01:28:20] 我的这个资产负债表要怎么改 [01:28:22] 这个第二张表要怎么改 [01:28:24] 现金流量表要怎么改 [01:28:25] 然后每一个操作 [01:28:29] 然后最后把它replace1遍 [01:28:32] 就得到了 [01:28:33] 当前的我可以 [01:28:35] 比如说提交出去的这个啊资产负债表 [01:28:38] 当然在这个这是这个总体的架构 [01:28:41] 然后这里面还有很多小的架构 [01:28:43] 比如说我这里用了一个curry [01:28:45] GPT写的代码很糟糕 [01:28:46] 就一开始写的非常非常长的代码 [01:28:49] 然后我就用了一个小的trick [01:28:52] 把它把它合并起来 [01:28:53] 我可以我可以给这个payroll加上一个参数 [01:28:57] 加上一个参数 [01:28:58] 然后就他就可以得到一个payroll [01:29:00] 相当于是这个参数state已经确定了 [01:29:02] 然后剩下的剩下的这样的一个一个函数 [01:29:05] 一个函数编程里面非常常用的 [01:29:07] 小小小的trick [01:29:09] 然后好处就是记账就不再是任何问题了 [01:29:13] 我可以 [01:29:14] 把比如说这是一个实际上记账发生的条目 [01:29:19] 咳比如说我要银行收支 [01:29:24] 比如说隔一段时间就会银银行收支会 [01:29:28] 要整理一下 [01:29:30] 那它在报表上面体现的所有的side effect [01:29:34] 都会 [01:29:35] 都会体现在体现在这里 [01:29:37] 然后我把他们全部都按时间顺序推一遍 [01:29:39] 就得到了当前的啊当前的报表 [01:29:42] 这是这是event source [01:29:44] 然后从这个例子里面 [01:29:48] 我们看到什么 [01:29:49] 如果我们设计一个信息系统 [01:29:52] 就我刚才说如果你用这个database去管理信息 [01:29:57] 那你的database很自然的 [01:30:00] 你管理的是现在 [01:30:02] 现在学校里面的状态是什么 [01:30:04] 每一个学生是什么 [01:30:05] 我们学校里面有哪些课程 [01:30:08] 每个学期有哪些课程 [01:30:10] 它代表一个当前的事实 [01:30:12] 然后这个状态是可变的 [01:30:15] 状态可变就意味着你一旦你的逻辑有错 [01:30:21] 逻辑改变你状态就很容易造成不一致 [01:30:27] 你就会从一个对的状态走到一个错的状态 [01:30:29] 而刚才我们讲什么样的好架构是好的 [01:30:32] 你能够生长的架构是好的 [01:30:35] 能够容容纳 [01:30:36] 比如说突然多了一个 [01:30:39] 多了一个新的需求 [01:30:40] 或者过去的某一个规则改掉了 [01:30:44] 然后当过去的规则改掉的时候 [01:30:46] 你这个时候你就需要为状态生成一个patch [01:30:51] 你就需要为状态生成一个补丁 [01:30:53] 然后这个状态的补丁 [01:30:54] 数据库的迁移就是一个非常危险的操作 [01:30:57] 因为你到底决定 [01:31:00] 比如说培养方案改了错了 [01:31:01] 修正了成绩更正了 [01:31:03] 成绩更正会带来大量的后果 [01:31:07] 比如说你的成绩从及格到不及格 [01:31:12] 打个比方 [01:31:12] 这个老师 [01:31:14] 假设这位同学你啧想去要分 [01:31:19] 然后老师老师说你不要了 [01:31:22] 然后你非要查卷子 [01:31:23] 然后查了卷子以后 [01:31:25] 然后老师发现当时救了你一手 [01:31:27] 那本来你最后那个题空着的 [01:31:29] 还给了你20分 [01:31:30] 然后好的 [01:31:31] 这下好了 [01:31:32] 他查了卷子 [01:31:33] 只能把这二分扣掉 [01:31:34] 然后就挂了 [01:31:35] 然后挂了以后 [01:31:36] 他对这个数据库就是一个 [01:31:39] 一个修改 [01:31:40] 一个可能就是不在延安业务逻辑里面的修改 [01:31:43] 对数据库是摧毁性的 [01:31:46] 因为你这门课从挂了以后 [01:31:50] 你的学分数要减一 [01:31:52] 然后你的学分数又会引起你能不能毕业 [01:31:56] 能不能准出 [01:31:57] 能不能选下一门课 [01:31:59] 就你的数据库内部其实已经不一致了 [01:32:02] 打个比方 [01:32:03] 你已经选上了他的先修 [01:32:05] 就你要选修操作系统 [01:32:08] 你要先修计算机系统基础 [01:32:10] 然后你计算机系统基础通过了 [01:32:11] 所以你选上了操作系统 [01:32:13] 然后你现在计算机系统基础挂科了 [01:32:15] 你还能选操作系统吗 [01:32:18] 它内部它天生就是不一致的 [01:32:20] 这个essential complexity里有大量的这种依赖关系 [01:32:25] 我依赖你 [01:32:26] 你依赖我好 [01:32:27] 那这个时候如果是一个当前状态的database好吧 [01:32:32] 你这个课好像这个成绩 [01:32:35] 成绩和是否挂科 [01:32:36] 这一来你就从一改成零 [01:32:38] 但是你对数据库剩下的部分你到底做了什么 [01:32:42] 你是很难评估的 [01:32:44] 很容易漏掉 [01:32:45] 很容易掉 [01:32:46] 所以为什么要用一般sourcing [01:32:48] event sourcing是说你不要你做的事情 [01:32:53] 不是维护一个当前状态 [01:32:56] 而是你要搞清楚 [01:32:58] 所有的原始数据之间的dependency [01:33:02] 比如说从一个这样的一个就是LM编译器 [01:33:06] 从一个记账条目到一个这个操作 [01:33:10] 这也可能会错的 [01:33:12] 这也可能会错的 [01:33:13] 但是错了以后 [01:33:14] 比如说我当期的这个账 [01:33:16] 我也会有人工来审核审核审核 [01:33:19] 如果发现这个错了以后 [01:33:20] 我可以把它修改过来 [01:33:21] 我们不会付出任何的代价 [01:33:23] 这个翻译错我就直接把它改正就可以了 [01:33:26] 然后如果我真的发生了一个 [01:33:28] 比如说像刚才我说的超额选课 [01:33:33] 选选课这个 [01:33:38] 选课你影响其他的其他的课程 [01:33:42] 你在这样的一个架构里面 [01:33:45] 你就会知道 [01:33:48] 你改了这个事实 [01:33:50] 比如说改了 [01:33:52] 然后我还换一个 [01:33:53] 比如说选课 [01:33:55] 比如说你通过 [01:33:57] 比如这个同学通过了这个ICS课 [01:34:00] 然后通过来ICS以后 [01:34:02] 有很多东西是后事后算出来的 [01:34:04] 然后当这个东西发生了反反转以后 [01:34:07] 因为你不维护当前的状态 [01:34:09] 你有所有的计算 [01:34:10] 你可以把这个计算重新全部做一遍 [01:34:13] 你哪怕用一个自动的数据流分析 [01:34:16] 你都知道 [01:34:18] 这个通过S从通过到不通过 [01:34:20] 它可能会影响哪些哪些结论 [01:34:26] 因为你把data flow就没make explicit了 [01:34:29] 而不是以状态的形式藏在那个当前当前状态里 [01:34:33] 而且event sourcing还有很可以带来很多非常好玩的 [01:34:36] 特点 [01:34:37] 因为它是这个replay based的 [01:34:40] 它是replay based [01:34:41] 你我当前的状态完全是由过去决定的 [01:34:45] 所以我可以用它来推演未来的状态 [01:34:49] 所以对我来说 [01:34:51] 我的记账系统最最大的好处 [01:34:55] 实际上是我可以假设 [01:35:00] 比如说打个比方这个流程 [01:35:02] 这个流程里 [01:35:04] 下单锁库存优惠计算 [01:35:07] 我现在在支付上 [01:35:08] 那么我现在可以在这个位置上直接推演出 [01:35:12] 就因为这个架构天生就支持 [01:35:14] 我可以推演出 [01:35:15] 如果风控成功状态会到什么样 [01:35:17] 如果风控失败状态会怎么样 [01:35:20] 也就是说如果我有个账本 [01:35:22] 我其实我可以在这个event sourcing的基础上去评估 [01:35:28] 我到底会不会破产 [01:35:29] 我破产的概率有多少 [01:35:31] 对方只要有一个模型 [01:35:32] 我只要知道 [01:35:33] 比如说我应收账款有多少 [01:35:34] 回不来 [01:35:35] 我就会破产 [01:35:36] 我就能够评估我的这个 [01:35:38] 为对未来做这样的评估 [01:35:40] 这都是一个好的架构 [01:35:42] 所能够所所能能够带带来的对 [01:35:48] 所以event sourcing [01:35:52] 为什么在解决这些问题的时候 [01:35:55] 会显得稍微要好一些 [01:35:56] 它没有办法消除这些essential complexity [01:35:59] 就这些依赖关系 [01:36:00] 我的先修课程的依赖关系 [01:36:03] 我的毕业的依赖关系 [01:36:04] 这些都是可以 [01:36:06] 都是事实已经存在的 [01:36:09] 但是event sourcing把事实试图和预期它分开了 [01:36:16] 然后如果世界没有按照你的预期发展 [01:36:18] 你就通过追加新的事实 [01:36:20] 就你不需要让那个工程师去手改一个数据库 [01:36:23] 然后让这个数据库进入越来越糟糕的状态 [01:36:26] 而是你可以追加一个 [01:36:27] 我现在领导认为这个同学就是可以毕业 [01:36:30] 其所有的东西都不管了 [01:36:32] 你就让他毕业 [01:36:33] 然后哈因为有这个 override 的 [01:36:35] 所以可以他可以 [01:36:37] 他可以毕业 [01:36:38] 所以如果你这个跳出问题看问题的话 [01:36:43] 就一句话现在是过去的结果 [01:36:46] 所以你有一个可查证可溯源的 [01:36:50] 计算过程你就显示的建模了 [01:36:54] 这个系统里面各种各样的data dependency [01:36:56] 虽然你没有就用一个数据结构去 [01:37:00] 一个是data dependency graph [01:37:02] 我这个数据是 [01:37:03] 你可以在当前数据库里面说 [01:37:05] 我这个表的这个数据是依赖这个的 [01:37:07] 这个是依赖这个的 [01:37:08] 这个是依赖这个的 [01:37:10] 你没有做这样的 [01:37:11] 你还是一个计算逻辑 [01:37:13] 还是说啊我因为有 [01:37:16] 因为我因为我有这个 [01:37:19] 通过了ICS [01:37:20] 所以我这个学分要加加几 [01:37:22] 要加上这个ICS的学分 [01:37:23] 我要从这个时候 [01:37:25] 通过ICS课 [01:37:26] 我就要取出在这个时刻ICS课的学分 [01:37:29] 然后我知道这个学分是几 [01:37:31] 以后我就可以把它加上去 [01:37:32] 所以所有的这个计算逻辑都是可以溯源的 [01:37:38] 因为计算机系统基础课的学分可能是变的 [01:37:41] 你如果有一般的source [01:37:43] 你就知道在当前这个时刻 [01:37:45] 计算机系统基础课的学分是几 [01:37:48] 你就不会出现 [01:37:50] 确实我们减了学分 [01:37:52] 然后那你学分还够不够毕业的 [01:37:55] 毕业的问题对 [01:37:56] 所以在这个意义上 [01:37:59] 我觉得SQL就是或者说关系代数 [01:38:04] 他犯了一个billion dollar mistake [01:38:06] 这个这个来自于那个Tony Hoare [01:38:07] 他调侃自己 [01:38:09] 他发明了他发明了空指针 [01:38:11] 然后他调侃自己的是一个billion dollar mistake [01:38:14] 因为因为因为有null pointer这个程序crash [01:38:17] 而crash的火箭可能也爆炸了 [01:38:21] 这样 [01:38:21] 然后其实关系代数的一个也 [01:38:25] 我也不能讲设计缺陷 [01:38:26] 就在那个时候 [01:38:27] 它当然是一个非常可以扩展的架构 [01:38:29] 非常好的架构 [01:38:31] 但他为了管理数据的时候 [01:38:33] 他没有把任何图灵可计算的计算关系 [01:38:36] 作为first class citizen管起来 [01:38:39] 这个是relation database [01:38:41] 他天生做的不是那么好的 [01:38:43] 也就是说如果如果我的database里面有两个 [01:38:47] 有两两个field [01:38:48] 就像就像我能不能毕业 [01:38:54] 就比如说学生学生里面有一个能不能毕业 [01:38:58] 然后这个毕业零或者一数据库 [01:39:01] 几乎都会这样设计 [01:39:03] 因为你必须要快速的把它查询出来 [01:39:06] 它不能用关系代数去建模一个非常复杂的 [01:39:09] 试图建模一个非常复杂的计算 [01:39:11] 那么在这个时候 [01:39:12] 这个零和一就成为了一个数据库里面 [01:39:15] 不一致的潜在的点 [01:39:17] 然后现在的框架用各种不同的方式来找补 [01:39:20] 你会看到这个m v m React data flow [01:39:22] Event sourcing [01:39:23] 都是试图把这种derived state和dependency显示化 [01:39:27] 所以我觉得去看一看这个老旧的东西 [01:39:32] 然后再去理解 [01:39:34] 比如说新设计 [01:39:35] 为什么我们要做event sourcing [01:39:38] 会啊就会觉得比较有意思