理解的乐趣与力量
摘要
对深刻理解代码与系统之乐趣与力量的反思,并警惕过度依赖LLM和捷径会削弱真正的精通。
<p><a href="https://lobste.rs/s/6vsofh/joy_power_understanding">评论</a></p>
查看缓存全文
缓存时间: 2026/06/24 11:57
# 理解的乐趣与力量
来源:https://binaryigor.com/the-joy-and-power-of-understanding.html
对我们所编写的代码和软件系统有更深的理解,不仅务实且实用,还能带来极大的愉悦感。
其用处显而易见:理解赋予我们对自己负责的系统与代码的*控制权和所有权*。我们无法修复或改变那些不理解的东西。
有趣的是,这种更深层次的理解不仅让我们成为工具的主人而非奴隶(https://binaryigor.com/programmer-in-wonderland.html),而且本身也充满乐趣,带来许多快乐。这背后很可能有坚实的进化原因——为什么理解会让人在心理上如此满足?这其实非常合理:那些能增强我们对环境控制能力的行为和特质,理应伴随着强烈的积极情绪。
但是,如果理解既令人愉悦又强大有力,**为什么我们常常倾向于跳过理解的挣扎,走捷径**,接受复制粘贴/生成的解决方案和泛泛的答案,而不进行深入分析呢?
## 人性
从本质上讲,我们是懒惰的生物,倾向于最小化能量消耗,最大化能量投入的回报。
**这种懒惰**可以成为自动化繁琐任务和流程的强大动力,但与此同时,**在学习、扩展知识技能以及由此带来的控制力、影响力和权力方面,它也是我们固有的缺陷**(在基于能力的健康层级中)。
在*需要理解*的背景下,考虑到网络上已有大量类似问题的答案和解决方案(https://binaryigor.com/who-controls-the-internet-and-how-it-works.html)——近年来甚至比以往任何时候都多,而且形式正好符合我们的需求,*这要归功于大型语言模型(LLMs)*——难怪人们经常通过走捷径来跳过理解。不幸的是,接受那些看似有效但无法解释其原理的东西,存在许多陷阱。
毕竟,当我们可以直接告诉 LLM 有哪些表以及想要检索的数据时,何必费心学习 SQL(或任何其他查询语言)的语法和内部机制呢?写一个凌乱的英文提示并复制粘贴结果,比自己学会如何正确操作要容易得多。
我听到有人可能会说:
> 如果我已经懂 SQL,为什么还要费心手动编写?LLM 可以更快地为我完成,而且我也不会损失什么,因为我本来就能熟练读写这种语言。
嗯,这要看情况;你今天可能能够阅读和理解,因为过去你反复手动操作过,但这种能力会随时间减弱。最终,我们确实会失去不用的东西——仅仅被动阅读不足以保持这些技能的敏锐。**当然,你可能会争辩说,有了 LLM(和其他可复用的解决方案),我们再也不需要亲自编写这类东西了,搜索、提示、阅读和验证 LLM 输出的技能才是我们需要的——这才是未来。**然而,考虑到 LLM 的工作原理(概率),我认为这是一个过于乐观且毫无根据的说法(暂不考虑 LLM 的长期可持续性)。无论如何,在最佳状态下,*LLM 和其他搜索引擎是力量的倍增器*——但我们必须先拥有这种力量,并保持其强大。*这种力量到底是什么?*它肯定不是提示和阅读输出的能力。如果我们一直在生成和复制粘贴解决方案(也许稍加改进),我看不出我们如何能够发展并保持这种力量的敏锐。幸运与否,挣扎是深度学习与掌握的必要组成部分。
当然,具体情境是关键。对于某些技能、我们不常使用且不在意的领域,这样完全没问题;但**我们经常使用的核心技能和知识必须尽可能保持强大——否则,什么能定义我们作为软件开发者和问题解决者的身份?** 难道不正是我们的知识、经验、判断力和技能吗?仅靠阅读他人和机器的代码与解决方案,是不可能发展甚至保持这些能力的——我们必须亲自积极参与构建和创造过程。
这就是为什么,*在理解的语境中*,我们必须定期与自己懒惰的天性作斗争——投入远超所需的精力,以不断扩展我们的知识、技能,进而扩大控制力、影响力和权力。**我们应该阅读文档和源码。理解所考虑解决方案背后的原因。熟悉我们的工具,了解它们带来的权衡。积极参与创造,自己设计解决方案和算法**——而不是仅仅搜索/提示并被动验证,寄希望于某个能用的方案;*不知怎的,总有一天,也许吧*。
## 短期与长期生产力的权衡
从实践角度来看,**我们是否总是需要完全理解所处理的代码和解决方案?**
当然不是——这取决于具体情况,而且是一个连续谱。
一个用于自动化低重要性、低风险操作的临时脚本?完全可以直接复制粘贴/生成。供两三个人使用的内部 UI/页面?同样没问题,也可以复制粘贴/生成。
而我们将长期拥有、维护和演进的代码和解决方案呢?它们应该用我们深入了解的语言和技术来创建,我们能够理解每一行、每一个字、每一个字符和/或配置选项(或者至少正朝着这个方向努力)。在这里,我们追求的是长期的理解、可维护性和生产力,而不是此时此刻最快、最大的输出。
**是否存在中间情况?** 即我们可能重用/生成一些只部分理解的东西,当需要时,我们需要花更多时间去理解、修复和修改?
如果我们正在开发一个*最小可行产品(MVP)*(https://en.wikipedia.org/wiki/Minimum_viable_product),不确定它是否合理/能否成功,或者在现有产品中开发一个实验性功能——适当降低质量和理解标准是合理的。毕竟,我们还不知道潜在的结果是否值得投入的努力。**我们可以将其视为承担认知债务——它让我们在当下更快,但将来需要偿还更多**(如果产品/功能成功,或者需要修复和/或修改)。不过,我们至少必须理解并验证到可以自信地说“它能用”的程度。*没有人会使用和付费购买故障产品半成品的功能。*我还想问:*生成某些东西是发现需求的最佳方式吗?*也许更好的办法是先获取这些知识,然后以正确的方式构建经过深思熟虑的产品/功能?但有时,生成、验证并从头重写可能也是一种合理的策略。
**类似的方法也可以应用于技术栈**——如果我们只是偶尔使用某种编程语言、库或工具,那么投入时间深入学习并掌握可能不太合理。复制粘贴/生成一些我们部分理解但能验证结果的东西,有时完全没问题。**但这里同样存在权衡——如果不经历学习阶段和挣扎(在这个阶段自然会更慢),我们就剥夺了自己掌握这些技术并高效使用它们的可能性。** 对于我们的核心技术栈——定期使用的语言、库、框架和工具——精通会带来成百上千倍的回报。我们不仅更加独立(因为本来就知道),更重要的是,*知识和技能具有复利效应*。我们知道得越多,能力越强,不仅自己能更快地构建东西,还能以越来越快的速度获取新知识和技能。提出新方案和新想法是持续提升能力的另一个附带好处。这就是为什么我们绝不能放弃学习,要不断将认知能力推向极限并超越。
## 输出导向与结果导向的度量
与上面提到的生产力话题相关的是定义问题。**我们到底如何理解、衡量和评估生产力?**
主要有两种学派:*输出导向* 和 *结果导向*。
**衡量输出很容易:**
- 产生了多少行代码?
- 开启并合并了多少个拉取请求(PR)?
- 实现了多少功能?
- 发现并修复了多少个 bug?
- 完成了多少任务?
- ……
但这种输出导向的方法存在问题:
- 很容易钻这些指标的空子:写冗余代码,开启更多但很小的 PR,人为将任务拆分成更小的,引入无用的功能等
- 我们如何知道衡量的是正确的事情?
- 更多的 PR 就一定更好吗?
- 不断增长的代码库是朝着正确方向的标志吗?
- 我们真的需要这个功能吗?删除一些不用的功能如何?
- ……
**如果我们更关注结果:**
- 生产发布终于稳定了——得益于新设计的 CI/CD 流程
- 代码被重构和简化——使维护和未来的变更更加容易
- 集成方案被重新设计——既能更快地添加新合作伙伴,又节省了计算资源
- 编写了更多的测试(https://binaryigor.com/unit-integration-e2e-contract-x-tests-what-should-we-focus-on.html)- 在 bug 有机会发生之前就发现了一些,并通过防止回归使未来变更更安全
- 向系统添加了指标和告警——主动展示各种潜在问题和 bug,使它们得以顺利解决
- 自动化了繁琐的手动流程——节省时间并消除了关键错误的可能性
- ……
当然,许多以电子表格驱动的管理者更喜欢输出而非结果,因为单独用数字衡量结果往往更难;评估它们需要更多上下文。
这是一个略微不同的讨论,但我认为*更明智的做法是更关注结果,而不是输出*。**长期生产力和理解与结果导向的度量更加一致:** 并非每个输出都有意义;更多往往不是更好,有时减少/删除某些东西实际上增加了价值。
## 日益增长的复杂性与基础的重要性
正如软件工程基本定理所述(https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering):
> 计算机科学中的任何问题都可以通过增加一个间接层来解决,除了间接层过多的问题。
现代软件开发的复杂性(https://binaryigor.com/modern-frontend-complexity.html)有时确实令人难以置信。这些系统是如何运作并得到维护的?有如此多的层次、组件和因素,似乎还在不断增加:
- **运行时与平台**——浏览器、服务器(虚拟与裸金属)、云、移动、桌面、嵌入式……
- **网络(https://binaryigor.com/networks-posts.html)**——各种层、协议和基础设施:HTTP、DNS、TLS、TCP、UDP、IP、WebSocket、WebRTC,数据库和消息代理/队列通常有自己的自定义协议……
- **安全、认证与授权(https://binaryigor.com/auth-posts.html)**——并非全部,但这里的大多数问题都根源于网络访问
- **操作系统**及其特性
- **虚拟化与容器化**——如果这还不够,还有像 Kubernetes 这样的容器编排平台(https://binaryigor.com/kubernetes-maybe-a-few-bash-python-scripts-is-enough.html)
- **数据库(https://binaryigor.com/dbs-posts.html)**——SQL、NoSQL、本地(嵌入式)(https://binaryigor.com/sqlite-db-simple-in-process-reliable-fast.html)、远程和分布式
- **高级编程语言**——Java、JavaScript/TypeScript、Python、C#、PHP、Ruby、Go、Rust、C/C++……需要一整套编译器、解释器和转译器才能最终执行为二进制机器码,这是计算机从根本上理解的唯一语言
- **库、框架与包管理器**——允许代码复用,但需要持续维护并可能引入漏洞
- **API 与外部服务**——我们可以购买某些功能并将责任委托出去,但这引入了可靠性依赖、往往不希望的耦合,以及数据传输和存储可能带来的法律和声誉问题
- **实践与方法**,不断涌现——CI/CD、测试驱动开发(TDD)、行为驱动开发(BDD)、GitOps、基础设施即代码(IaC)、领域驱动设计(DDD)、事件驱动架构(EDA)、事件溯源、命令查询职责分离(CQRS)、服务端渲染(SSR)、客户端渲染(CSR)、整洁架构、六边形架构、垂直切片架构、模块化单体、微服务、微前端……
- **LLM 与 AI**——它们在哪些方面有帮助,哪些方面实际上阻碍了系统的开发、维护和演进
- ……
**面对令人眼花缭乱的复杂性该怎么办?** 甚至有可能部分理解吗?有这么多层次、组件和因素!
别担心,有些好消息:
- 许多软件系统被过度设计,可以大大简化——我们可以为这个过程做出贡献,而且它们往往没有第一眼看上去那么复杂
- 在任何特定时期,我们通常都有专门领域,只需要掌握软件开发景观中的一小部分——对于其余部分,我们只需有所了解
- 如果我们花更多时间掌握*基础*,并专注于识别所用工具背后的通用模式和基本原则——这将给我们带来巨大的杠杆作用,是学习任何新工具、新方法或新技术的绝佳捷径
我们来关注后者。
**什么是基础?**
> 基础是工具、库、框架、协议以及用于开发软件的各种组件背后最基本、很少变化的规则、约束和机制;同样也是支配计算机和计算的核心原则。
一个较为全面的列表包括:
- **计算机体系结构与硬件**——CPU 架构、指令执行、内存层次结构、寄存器、缓存和存储设备。机器到底是什么?
- **机器码、汇编与高级编程语言**——为什么汇编器、编译器和解释器不可或缺?不同类型语言的权衡是什么?
- **操作系统**——它们是什么?为什么需要它们?关键抽象:进程、线程、调度、锁、同步、虚拟内存、文件系统、进程间通信(IPC)、系统调用(syscalls)、I/O 操作……
- **算法、数据结构与复杂度分析**——它们几乎在每一段软件、每种编程语言、库、框架和工具中都被利用并至关重要
- **网络**——计算机如何相互通信。我们可以对它们做哪些假设?它们可靠吗?吞吐量和延迟如何?为什么它们分成不同层次?
- **数据库与数据系统**——ACID(https://binaryigor.com/mysql-and-postgresql-different-approaches.html)、事务与隔离级别、索引、存储
相似文章
迈向可理解的软件
本文批判了当前的编程实践和对大语言模型的依赖,反而主张通过更好的抽象、文档和软件栈来使代码更易于理解和维护。
理解才能参与
关于Geoffrey Litt在AIE 2026上演讲的反思,讨论了理解AI编程代理所做出的代码变更的重要性,以避免认知债务并在过程中保持积极创造力。
理解成为新的瓶颈
Geoffrey Litt认为,随着AI代理生成更多代码,理解这些代码成为了新的瓶颈,他提出了代码解释文档、测验和微世界等技术,帮助人类在创意过程中保持参与,而不仅仅是验证正确性。
Martin Fowler:技术债、认知债与意图债
Martin Fowler 反思 AI 对代码质量的影响,指出人类的“懒惰”反而促成清晰抽象,而 LLM 则可能用不必要的复杂性把系统拖胖。
# 代码(更)廉价了
Carson Gross(htmx 的创建者)认为,尽管 AI 让代码生成的成本降低了,但理解代码的成本却在上升。他警告开发者警惕"魔法师学徒"陷阱——让 LLM 生成难以驾驭的复杂代码。他提倡增量式地使用 LLM,并保持对代码库的深度理解。