本贾尼·斯特劳斯特卢普:C++
Bjarne Stroustrup: C++
C++ 设计者谈语言的设计取舍、零开销抽象的理念,以及编程教育与好代码的标准。
你踩下刹车踏板的时候,踏板和刹车片之间没有机械连杆。中间隔着一台计算机。
这台计算机上跑的代码,很多是用一门 1979 年开始写的语言实现的。C++。
YouTube 的大量关键后端组件是 C++ 写的。Google、Facebook、亚马逊、推特、微软的多数应用、多数数据库系统,还有那些在物理世界里真正干活的东西:汽车、机器人、发射进太空的火箭。这些东西的共同点是,出错不能靠"重启一下"解决。
2019 年,Lex Fridman 把这门语言的作者本贾尼·斯特劳斯特卢普(Bjarne Stroustrup)请到播客里聊了一个多小时。这场对话没停在语法特性上,它更像一个人在解释自己四十年的取舍:什么必须快,什么必须可靠,什么宁可不要。
第一门真正爱上的语言,和那个算星形的下午
斯特劳斯特卢普写的第一个程序,是大学二年级的事。任务是算出一段曲线的形状,然后把周长上的点连起来,生成星形图案。输出设备是一台纸带打印机。
他学编程算晚的,第二年才正式开始。第一门让他产生感情的语言是 Algol 60。之后的路走得不太顺:Fortran 没让他心动,Pascal 也没有。
Pascal 总是在挡我的路。(斯特劳斯特卢普)
他转向汇编,觉得那反而更有意思,接着写了大量 BCPL。BCPL 时期是他第一次靠编程赚到钱,硕士学费就是这么付掉的。
真正打开新世界的是 Simula。
Simula 是 Algol 60 的扩展,本来为仿真而做。做这个语言的人顺手发明了面向对象编程、继承、运行时多态。斯特劳斯特卢普在这里第一次意识到一件事:程序的复杂度可以随代码规模线性增长,而不必随规模平方增长。
你完全可以做到很好的模块化。这对我来说是个惊喜。
另一个惊喜关于类型系统。Pascal 的类型系统处处跟他作对,Simula 的类型系统比 Pascal 更严格,却处处帮他的忙。这个反差后来成了 C++ 的一条底层信念:类型系统要强,同时必须可扩展、可变形。
那个年代的 Pascal,带着一种道德腔调
他解释这个差别的时候说得很直接。Pascal 是 Niklaus Wirth 能为当时的需求设计出的最简语言。它有一种高度说教的语气:如果你能用 Pascal 表达,那就是好的;表达不出来,那就是不好的。
Simula 反过来,鼓励你自己建类型系统。你不必把自己塞进 Wirth 划定的那个世界里,你可以为自己手头的问题现造一套类型。
这已经很接近今天说的领域特定语言(DSL)。你真正在建的,是一组类型以及类型之间的关系,这组关系让你能用贴合应用的方式描述问题。
顺带一个冷知识:所有用 class 这个词表示类型的语言,都是 Simula 的直系或旁系后代。Simula 的设计者是一群数学家,他们不按"类型"思考,想的是集合和元素类,于是把类型叫成了 class。
消防车转弯,为什么需要继承
很多人对继承的理解停在教科书上那个"动物→狗→猫"的例子,从来没搞明白它在解决什么问题。斯特劳斯特卢普给了一个他亲眼见过的例子。
你有一个车辆集合:自行车、轿车、消防车、坦克。现在你让它们左转。如果你不知道它们是什么,你得写一大堆判断:如果是自行车,按自行车的方式转;如果是轿车,按轿车的方式转;如果是消防车,按消防车的方式把方向盘打死。
代码会变成一坨巨大的 case 和 if 嵌套。
有了继承,你只需要对着基类说一句"转"。每个具体类型自己知道该怎么转。
60 年代有人真拿它去模拟和优化某个地方的紧急救援调度。(斯特劳斯特卢普)
这个例子里藏着一个很硬的需求:你不可能每来一种新车型就重写一遍调度代码。继承和运行时多态之所以值得存在,是因为真实系统会不断长出新东西,而调度逻辑必须稳如磐石。
为什么 Lisp 不是他的菜
访谈里 Fridman 提到 Lisp 是自己最喜欢的语言之一,问斯特劳斯特卢普怎么看。回答很诚实:Lisp 当然重要,当然有意思,很多人在里面写代码,然后在要上生产环境的时候,把它重写进 C 和 C++。
他的领域有一组绕不过去的约束:性能、可靠性、可部署性、硬件成本。
我不喜欢东西太动态。写一段既完美又灵活的代码,能塞进一台小计算机,还能装进波哥大的电话交换机里,这个概率有多大?(斯特劳斯特卢普)
他举了一个具体场景:某个周日的深夜,波哥大的电话交换机崩了,你发现自己进了调试器。周围有个程序员吗?
概率是零。
他很清楚自己服务的只是少数派。全球七八成的代码不在这种约束下,写 Web 前端和数据分析脚本的人完全有资格享受动态语言带来的速度。但总得有人负责从那些高级而灵活的语言,一路下到硬件。他选了这个位置。
在这个位置上,一门语言的价值不体现在写起来多舒服,而体现在写出来的东西十年、二十年、三十年之后还能在苛刻条件下跑。
五门语言,第二门最重要
他说一个专业程序员应该懂至少五门语言。理由跟会不会用它们没关系,学语言这件事本身就是训练。
只会一种语言的人,就像一个只待在自己文化里的人,很容易以为自己的活法就是唯一的活法。(斯特劳斯特卢普)
他估计自己懂二十五门左右。他也承认那个年代学语言容易,语言本身小,不用连整套编程环境一起啃。
他随手挑了五门:机器码、C++、一门函数式语言(Haskell 或 ML 都行)、一门能快速出活的语言(JavaScript、Python、Ruby 随便挑一门)。
关于机器码,他给了一个今天写高层代码的人必须记住的判断。手写底层代码换性能这件事已经过去了,C++ 优化器比人聪明。但如果你不懂机器码和机器架构,你根本欣赏不了优化器在做什么。
更实际的一点:你把缓存弄乱了,性能直接掉一百倍,这个惩罚跟用什么语言无关。
他说,现在他最喜欢的优化方式,是把代码里那些"聪明的部分"全删掉,看看还跑不跑得快。有时候删完更快。
所以他需要的其实是抽象机制,让他能把代码写得紧凑、写得让优化器看得懂。
Commodore 64 上的 Pong,和一条不断缩短的代码
他提到 Jason Turner 在 CppCon 上的一场演讲。那个人决定在一台 Commodore 64 上写 Pong。这台机器的特点和今天的微控制器很像:有专用硬件,内存小得可怜,速度慢。
现场直播的过程是这样的:他先用相当直白、相当低层的方式写,然后用 Compiler Explorer 实时看到 C++ 编译成汇编。接着他开始提高抽象层次,用 C++11 的特性,代码变得更干净、更好维护、更短。
它一直在变短。
斯特劳斯特卢普的结论是,他不可能在任何合理的时间内手写出编译器从现代 C++ 生成的那种质量的机器码。他甚至说,那段看起来像 C 的代码,变成机器码之后反而更丑、更大。
这个演示的价值在于它把"抽象"从一个审美偏好拉回到了工程事实:能被优化的抽象才有资格存在。
刹车上没有机械连杆
回到开头那个画面。汽车刹车踏板和刹车系统之间已经不是直接的机械连接,中间是计算机。这台计算机最好别出错。
现代的汽车要用几百万行代码,自动驾驶和车道保持这类系统更甚。Fridman 提到他跟监管者聊过,那些人对软件安全性极度紧张,因为这些代码最终会做出可能致死的决策。
斯特劳斯特卢普的回答绕开了一个常见的思维陷阱。
安全性、性能、保密性,都是系统属性。人们习惯一次只看系统的一个部分,然后说"这块是安全的"。你如果真想要可靠、想要性能、想要安全,你必须看整个系统。(斯特劳斯特卢普)
他甚至反过来讲了一层:把单个零件打磨到完美,可能并不是获得最高可靠性的最佳路径。
C++ 没有类型安全,你可以把它弄崩。他的回应很干脆:任何跑在计算机上的东西我都能弄崩。我要真想进你的机器,我大概会去试 SQL 注入,而不是去破你的类型系统。
识别丑代码比识别美代码容易
怎么让代码变可靠?他的答案只有一个词:简化。
不是先上测试框架,也不是先上运行时检查。第一步是让代码变少,让出错的概率变低。他引了爱因斯坦那句老话:尽可能简单,但不能更简单。
具体怎么做?他给了一个判断标准。如果你思考问题的方式没有体现在代码里,那代码里就缺了一块,那一块只存在于你脑子里。看代码的人能看到它做什么,看不到你想什么。
当你能把想法直接表达出来,代码就变得可维护、易找错、易修改,也更好测试。顺便,它跑得更快。于是你需要更少的机器,可能出故障的硬件也就更少。
至于怎么判断一份代码是不是干净的,Fridman 问得很直接:假设把福特 F-150 的代码给你,你能看出来这是不是一团乱麻?
答案是有经验的开发者能闻出来。
识别丑比识别美容易。原因是美有时候来自创新和不寻常,你得想一会儿才能领会。而混乱有很多共同点。(斯特劳斯特卢普)
他这些年一直在把"好代码长什么样"写成规则,目前这一版叫 C++ 核心准则(C++ Core Guidelines)。他的表述很克制:规则本身造就不了好程序员,但它限制了损害的范围,减少了犯错的可能性。
他用了一个很漂亮的类比。把所有雕刻的规则写进大理石,也变不出米开朗基罗。写一个好程序,除了手艺,还需要一点灵感、理解和天赋,而灵感没法监管。你能规定的只有手艺那一层。
静态分析真正的作用是教育
他很看好静态分析,理由在于它能抓"用法",抓 bug 只是附带。
他举的例子是资源泄漏。在 C 那一层,你有一个 malloc,就必须有一个配对的 free。如果两个都在同一个函数里,分析起来很容易,看到一个 malloc,就该在附近找到一个 free。但如果中间隔着一堆代码,指针被传出函数,free 在别的地方发生,问题立刻变成几乎不可解。
静态分析跑一遍程序,会找到一些泄漏,也会找到大量它分析不下去的地方,因为结果依赖运行时,或者依赖分析器够不够聪明,或者干脆跑不完。
一旦你把静态分析和一套使用规则结合起来,你就能看出规则为什么被违反。你不需要解决全部问题,你需要的是别掉进那个无底洞。
他对静态分析的评价很高:它像一个教学工具,在你写未来代码的时候给你指路。
更深一层的原因是他对错误处理代码的悲观。代码一旦跑起来才报错,你就必须写错误处理器。错误处理代码是最难写的代码之一,因为出问题的时候,你往往并不确切知道出了什么问题。你不知道问题是什么,你凭什么恢复?
所以他宁愿在代码跑起来之前就把错误找出来。
抽象不该比手写慢
这里到了整场对话的技术核心,零开销原则。
如果你有一个抽象,它相对于等价的低层代码,不应该带来任何额外成本。(斯特劳斯特卢普)
举矩阵乘法。用矩阵这个抽象写出来的乘法,应该快到让你没法通过退回数组和指针再快一点。
实际上有人写出来了,而且比 Fortran 快。原因很反直觉:一旦你有了正确的抽象,编译器就能消除临时对象,做循环融合这类优化。这些事手工做极其痛苦,在低层语言里几乎做不好。
这个故事有个流传很广的后续。八十年代,他的朋友 Doug McIlroy 在演讲后问了一句:如果我把你这段 C++ 改写成 C,会快多少?答案是会慢很多。
他特别澄清了一点:他从来没说过 C++ 是面向对象编程语言。这句话被引用了无数次,但原话不长这样。
我说的是 C++ 支持面向对象编程,也支持其他技术。(斯特劳斯特卢普)
这个区别有实际后果。复杂而有趣的问题,最好的解法通常要同时用到面向对象、数据抽象、函数式,以及传统 C 风格代码。一个只允许一种范式的语言,会在某个环节逼你绕路。
他在设计 C++ 的时候很早就明白一件事:不能只是往语言里加特性。看到什么好看就加,听谁要什么就加,一个个加进去,最后得不到一个协调的整体。你需要一组原则来决定什么该进、什么不该进、一个特性要改到什么程度才能进。
这些原则里大部分不是语言技术问题。比如"不要破坏静态类型系统",理由很朴素:他要东西在合理的硬件上可靠地跑。
编译器的性能从哪里来
零开销原则靠什么实现?三样东西同时到位:编程技术、语言特性、实现技术。
他提到 Brian Kernighan 说过的一句话,C++ 能成功,部分原因是他早期编译器里下的功夫。他自己写了不少用这套东西的代码。
一个人不可能同时精通所有领域。他举了并发编程的例子:你需要懂并发算法,需要懂无锁编程的细节,需要懂编译器技术,还要懂应用领域,比如某些图形学或者 Web 服务器那类负载。这些东西塞不进一个脑袋,但一个两三个人的小团队可以做到。
四种编译器,和他讨厌的那个词
为什么 C++ 有很多种实现?没人做过这个决策,它从一开始就是既成事实。
他设计 C++ 的时候,世界不是一台 x86 加 Linux。他想的是要利用各种已有的后端基础设施。然后他发现用户在用二十五种不同的链接器。他可以写一个自己的链接器,但他不可能写二十五个,同时还推进语言本身。
所以多种编译器是从一开始就存在的前提。今天至少有四个前端:Clang、GCC、微软、EDG。EDG 供应了大量独立组织和嵌入式行业。后端则多得没法数,嵌入式世界的芯片架构差得太远,单一实现从来就不是选项。
他顺手说了一句:他讨厌单一文化。
单一文化的危险在于,拥有它的人会僵化,没有竞争,没有创新动力,反而有大量动力去给变化设障碍,因为这个世界对他们太舒服了。GCC 曾经很好,后来有点僵,Clang 出现之后微软也变好了很多。少数几个前端的存在,给标准符合性、性能、错误信息质量、编译速度都施加了压力。
构造函数和析构函数
问到他个人最喜欢的 C++ 特性,答案很基础:构造函数和析构函数。
构造函数为一个对象建立好使用环境,析构函数在结束时清理掉所有烂摊子。这是 C++ 的关键,是它不必依赖垃圾回收的原因,是它能给出可预测性能的原因。
这个想法有个更广为人知的名字:RAII,资源获取即初始化。他的自评是,这是他为什么不该去做广告的例子。这么好的一个想法,被他起了这么个名字。
时间点也很早。1979 年,他开始做那个当时叫"带类的 C"的东西,大概第二周,构造和析构就进去了。
逻辑是一步步推出来的。你控制了对象的出生和死亡,就必须控制拷贝,因为拷贝是另一种创建对象的方式。控制完拷贝,你还得能把对象搬来搬去,于是有了移动语义。这组操作定义了一个 C++ 类型能用什么方式存在。
Fridman 把这件事抬到了哲学层面:创建一个类型的实例、释放它的资源,就像出生和死亡定义了一个人的生命。
斯特劳斯特卢普接了这句话,并顺势说,哲学很重要。没有哲学就做不好语言设计,因为你决定的其实是人们能表达什么、怎么表达。
概念为什么拖了三十年
模板是 C++ 泛型编程的基础。编译器看到模板和参数,把两者结合,生成一段代码,效果等同于你专门为那个类型手写了一份。信息拿得更全,所以有时生成的代码比人写的还快。
代价是调试。优化后的代码从来就不好读。1983 年那个 C++ 前端就已经做了前端和后端两层优化,生成的 C 代码是给机器看的,不是给人看的。加上模板之后更糟,因为优化器会把代码不同部分的信息拖到一起。
实际做法就是关掉优化器调试,先把逻辑层面搞对。
真正难的部分在概念(concepts)。
1987 年他做模板的时候,想要三样东西:极度灵活,能表达他想象不到的东西;和手写代码一样快或更快;能表达模板对参数的要求,也就是接口约束。
前两个他做到了,第三个困扰了他二十多年。函数参数的类型检查是他推动进入 C 的,他很清楚好接口的价值。但模板的接口约束,他和当时所有人都不知道怎么同时满足这三点。
我必须有这个工具,否则它就不算 C++。(斯特劳斯特卢普)
2003 年前后,Gabriel Dos Reis 开始系统分析这个问题,印地安那大学的一个老朋友也启动了项目。他们以为两三年能搞定,那样 C++11 就会在 2006 或 2007 年发布。
结果核心思路就是错的。
我们太因循了。它不太贴合 C++。它对隐式转换的支持不好,对混合类型的算术运算支持不好。很多东西来自函数式社区,而那个社区处理多类型的方式跟 C++ 不一样,对能表达什么有更多限制,也没有那么严苛的性能要求。(斯特劳斯特卢普)
他们试得很苦,也取得了一些成果,但最后编译不够快、用起来太难、运行不够快,除非优化器达到当时还不存在的水平,而那个水平今天也还没到。
最后落地的方案是另一条路:一组参数定义一组操作,通过一张交互表来查找,思路类似虚函数表,然后再想办法优化掉这层间接。这个概念在 C++20 里,2019 年已经在 GCC 和 Clang 里可用好几年,微软即将跟上,标准库也已经在用。
概念本质上就是编译期谓词。你能问一个类型:你是序列吗?你有 begin 和 end 吗?你是随机访问序列吗?你有下标吗?你的元素类型支持小于号吗?
以前模板说的是"我接受任何类型",现在它能说"我接受满足这些条件的类型"。
这个词是 Alex Stepanov 起的,C++ 泛型编程之父。他的理由很直白:这些都是一个领域里的基本概念,就该叫概念。
一年开三次会,一个组织一票
标准化的起源很有意思。八十年代初,IBM 和 HP 各来了一个人,坐进他办公室,说要标准化 C++。
他觉得莫名其妙:这语言还没做完,没到能正式标准化的程度。
对方的回答是:你不明白。我们的组织依赖 C++,我们不能依赖一个属于别家公司的东西,那家公司可能是我们的竞争对手。我们当然可以信任你,但你可能被公交车撞了。
后面还有半句更关键的:你最好参与进来,否则我们就自己搞,而我们知道你做得更好。
连哄带劝,1989 年底在华盛顿开了第一次会,二十五个人的规模,据说创了这类会议的记录。会上先给大家讲 ANCI 的规则。
这套流程完全开放:交一笔会费就能参加,当年不到一千美元,现在大约一百八十美元。一年开三次会,每次一周,技术讨论,然后投票。投票是一个组织一票,所以 IBM 带十个人来也压不住场面。
他对比了一句:这比让他一个人决定要好,也比让 AT&T 这样的单一组织决定要好。标准化很不愉快,很像民主。而民主,按丘吉尔的说法,是最坏的制度,除了其他所有制度以外。
1990 年第一次技术会议,1998 年发布第一个标准。当时他们想,这次学会了,下一个六年就能出来。
结果花了十三年。
原因是第二系统效应:既然已经知道怎么做了,那就做得更好一点吧,于是野心膨胀,时间拉长。还有一个规律,越是临近发布,越有人冒出一个绝妙的主意,非加不可。
C++11 那次他们赌对了,加进去的东西确实救了 C++。之后有人想复制这个成功,但没那么戏剧性,而且越拖越久。
解决办法很简单:发得更勤。十年周期里出现一次延期,你意识到的时候已经过去十一年了。三年周期里出现延期,三四年就能补回来。于是 14 准时,17 准时,20 也会准时。
这个可预测性对编译器实现者极其重要,因为他们有了明确目标,并且真的交付。他的原话是:C++11 之后,多数编译器花了两年才做到"够用";14 的时候情况好了很多;17 的时候几乎所有厂商都在当年发货;而 20,因为大多数实现在 2019 年就已经提前交付,他有底气说这次也会准时。
语言设计是哲学问题
Fridman 问他,有没有可能未来出现一套统一的语言设计原则,就像物理学追求统一理论那样。
斯特劳斯特卢普把两边的年龄摆了出来。现代物理从伽利略算起,七百年。现代计算从 1949 年前后算起,七十年。差了十倍。而且物理学不用操心"用户"这件事,语言设计得操心,因为写程序的是人。
所以他觉得这个领域非常不成熟。但在哲学层面,确实存在可以达成共识的原则:别泄漏资源,别在运行时处理你本不必处理的错误,别破坏你自己定下的类型系统。类型系统可以有很多种,但一旦选定了一种,就不要打破它。
他也看到了实际的收敛。C++ 早年采纳的 RAII,二十年后在垃圾回收语言里以 disposal pattern 之类的形式出现,那些语言也在努力不要事事依赖 GC。
他讲了一段很有意思的往事。有一次他和 Common Lisp 的几位设计者坐在一起聊语言和语言特性。一开始什么都没谈成,彼此看不顺眼,他甚至开玩笑说自己不喜欢那些括号。聊着聊着突然通了:他描述自己遇到的某个问题,以及他是按什么原则解决的;对方说他们遇到过另一个问题,也是按同样的原则解决的。他们一起走过了 C++ 的大块内容和 Common Lisp 的大块内容,发现两边的推理方式、底层原则其实高度相似。
不同的是约束条件和目标用户。
原则可以共享,取舍不能。这是他对"语言统一"这个问题最诚实的回答。
84% 的准确率,和那剩下的部分
Fridman 把话题引向机器学习。他的类比是:写程序是造一个接受输入、产生输出的东西;机器学习是在数据上训练出这么一个东西。区别在于后者是模糊的、有噪声的,像人一样。
斯特劳斯特卢普的回答毫不含糊:那是另一个世界。
我不喜欢模糊。人们说希望人人都能编程。我不想人人都来编我的飞机控制器和汽车控制系统。我希望那是工程师做的,是专门受过教育、专门被训练来做这件事的人做的。(斯特劳斯特卢普)
他把 C++ 的定位说得同样直白:这门语言服务的是专业人士,是一把锋利有效的工具,交给那些追求精确的人。
你不会让不懂数学的人去做计算。掰手指头是送不上月球的。
然后他给出了一个具体的数字判断。Fridman 提到 99.8% 的准确率,他的回应是:他看到的更多是 84%,而且要靠大量血汗才能推到 92.5%。
84% 在你做预筛选、把结果交给人类复核的场景里完全够用,甚至比人做得好。但它在涉及生命的场景里不够。这个数字划出的是适用边界,跟技术本身好不好没有关系。
他对自动驾驶的担忧落在一个非常具体的地方。他听说过一种设计:当 AI 系统遇到处理不了的情况,就把控制权交还给人类。而那个人可能正在看书,可能正在睡觉,他被叫醒后有三十秒,或者只有三秒,要搞清楚 AI 没搞定什么,然后做对。
这很吓人。人和机器之间的交接成本,怎么算?(斯特劳斯特卢普)
他说完又补了一句:这个问题被摆出来了,还谈不上无解。他自己的偏好是干脆别依赖人。开核反应堆也好,开自动驾驶也好,最好是设计一个永远不需要向人求助的系统。
这句话从一个造了四十年可靠系统的人嘴里说出来,分量不一样。AI 可不可靠是另一回事。一个要求人随时待命的系统,等于把两种性质完全不同的不可靠叠在了一起。
他也承认这些系统就架在 C++ 之上。这也是他必须持续关注那个领域的原因,尽管他明确说自己永远不会成为那方面的专家。这又回到他反复强调的那件事:今天没有哪个大系统是用一种语言写的,而且理由充分。不同领域需要不同工具、不同原则,然后它们互相接触。
四十年里最让他骄傲的,是望远镜和火星车
Fridman 问他,回头看这辈子的工作,哪一刻让他觉得"我干得不错"。
答案没有落在任何一个语言特性上。C++ 是几个好想法、大量苦工、一点运气的组合。他有几次想离开,但总被拽回来。一半是因为他在这个领域最有效率,另一半是因为在 C++ 的上下文里做事,影响面大得多。
如果我明天做对了什么,有四五百万人会捡起来用。如果我去别的领域,我得先学,然后得把它建起来,然后还得看有没有人愿意用。(斯特劳斯特卢普)
真正让他撑下来的,是能看到人们拿它做出了什么。
他去看过莫纳克亚山上的望远镜。他去看过福特怎么造车。他去过 JPL,看过火星车是怎么做出来的。他说了句很朴素的话:这么多酷东西在发生,而大部分酷东西是相当不错的人做出来的,有时候还在相当不错的地方,剑桥、索菲亚·安提波利斯、硅谷。
代码是中心,但不止是代码。
这场对话真正在讲什么
把它当成一场关于 C++ 的访谈很容易,但里面那些判断的适用范围远大于一门语言。
他反复回到同一个结构:先确定约束,再谈方案。他的约束是性能、可靠性、可部署性;他的结论是抽象必须免费,标准必须准时,错误必须在运行前抓住。所有具体的语言特性,都是从这个约束推导出来的结果,没有人先喜欢上某个特性再回头找理由。
他对简化有一段话值得单独记住。如果代码里没有体现你对问题的思考方式,那么你的思考就只存在于你脑子里,代码看不见它。而看不见的东西没法维护、没法测、没法改。他后面那句更妙:当你能直接表达想法时,代码顺便也跑得更快了。
还有一层常被忽略的判断:安全、性能、保密都是系统属性。单点做到完美,不等于系统可靠。这个视角对今天做 AI 系统的人尤其刺人,因为整个行业的叙事都集中在模型这一层。
最后是他对"什么不该做"的坦率。他讨厌单一文化。他承认 C++ 被用在比特币挖矿上,耗电量相当于瑞士,而且主要服务犯罪。他明确说不希望所有人都来编程。一个做了四十年工具的人,能清楚说出自己的工具不该用在哪,这种自觉比任何技术洞见都稀缺。
他在访谈最后被问到还有什么要说,只留了一句:会做得更好。
如果你想把这场对话变成行动
- 学一门和你日常工作范式完全相反的语言。你已经会 C++,去写半年 Haskell 或 Lisp;你只写 Python,去写一段真正需要手动管理内存的代码。第二门语言带来的收益远大于第三门。
- 下次性能不够时,先删代码,别先加优化。把那些"聪明的部分"拿掉,看是否反而更快。缓存和分支预测的惩罚比指令数更狠。
- 给你的模板或泛型接口加上约束。如果你在写 C++20,用概念;如果在写别的语言,用类型标注或接口声明。让"我接受任何类型"变成"我接受满足这些条件的类型",报错信息会从一屏变成一行。
- 把资源管理和对象生命周期绑在一起。构造函数获取,析构函数释放,不要写手动清理路径。这条原则在任何有 RAII 或类似机制的语言里都成立。
- 在写第一行代码之前,先写下三条这个项目不可违反的原则,比如不破坏类型系统、不在运行时处理可预见的错误、不引入无法静态检查的隐式状态。原则要在特性之前定,否则你只是在堆功能。
- 做安全关键系统时,别把"AI 处理不了就交给人"当兜底方案。先算清楚人要多久才能理解现场,如果这个时间不够,那这个设计就是错的。
- 读一遍 C++ Core Guidelines。不一定用它写代码,用它训练自己闻出坏代码的能力。识别丑比识别美容易,而这项能力可以刻意练习。
本集资源与出处
- 视频在 YouTube 收看
- 音频官方 MP3 直链来自 lexfridman.com 官方播客 RSS
- 官网Lex Fridman 官网本集页面
- 文稿HappyScribe 版文稿