首页 › programming › 嵌套神课:缩进是一张复杂度账单

嵌套神课:缩进是一张复杂度账单 —— CodeAesthetic《Why You Shouldn't Nest Your Code》深度解读

概述:为什么值得为这条视频写三千字

在 YouTube 编程区,CodeAesthetic 是一个奇特的存在:不用段子、不发新闻、不蹭框架热度,只用手绘动画讲“代码为什么难读”这类最古老的问题。他的《Why You Shouldn't Nest Your Code》(为什么你不该嵌套代码)是频道的代表作之一,常年出现在“每个程序员都该看”的推荐清单里,播放量达数百万级(据 YouTube 频道页公开数据,本次成稿时抓取通道受限,未能实时核验精确数值)。

这条视频只有不到十分钟,却把一个几乎所有团队都在犯、却很少被正面讨论的问题讲透了:代码的嵌套深度,本质是一张记在你团队头上的复杂度账单。每多一层缩进,账单就翻一页;等到没人敢动那段“箭头代码”时,利息已经滚到了还不起的地步。本文按视频的论证顺序拆解它的核心主张,把背后的理论一路追溯到 1976 年的圈复杂度论文和 1968 年那封著名的信,再讨论哪些说法证据充分、哪些被简化了,最后给出三条明天上班就能用的建议。

视频基本信息

项目 内容
视频标题(英文原题) Why You Shouldn't Nest Your Code
标题(中文参考译名) 为什么你不该嵌套你的代码
频道 CodeAesthetic
领域 代码质量 / 软件设计(编程与开发)
发布时间 2022 年(据 YouTube 频道页面,本次未能实时核验精确日期)
播放量 数百万量级,为频道播放量最高的视频之一(据频道页面公开数据,未实时核验)
观看方式 在 YouTube 检索频道 CodeAesthetic 或视频原题即可找到:https://www.youtube.com/@CodeAesthetic

说明:本文成稿时网络抓取通道受限,播放量与日期等动态数据以 YouTube 频道页公开信息为准并已标注;视频标题、频道名与核心论点均来自该频道的公开内容。

核心主张拆解:视频按什么顺序说服你

这条视频的说服结构非常工整,一共五步,环环相扣。跟着它的叙事顺序走一遍,你会发现自己写过的每一坨深层嵌套代码都有据可查。

第一步:先让你难堪——箭头反模式

视频开场直接展示一段典型的“箭头代码”:函数从左上角开始,每处理一种情况就多缩进一层,if 套 if 套 for 再套 if,等主逻辑真正开始干活时,代码已经缩进到了屏幕右边三分之二处,整个函数的形状像一支射向右下方的箭头。这个形状有个流传已久的名字——箭头反模式(Arrow Anti-Pattern)。

它的要害不是“丑”,而是读者被迫同时记住所有外层条件。你读到第 7 层缩进的那行代码时,脑子里必须悬着 6 层“我们此刻已经确认过什么”的上下文。写代码的人一天,读代码的人一年——视频反复强调:代码是写给人看的,恰好能在机器上运行,只是顺便。

第二步:把“丑”翻译成数学——路径数在指数爆炸

紧接着,视频给出了最有力的一击:嵌套不只是观感问题,它是可测性问题的数学等价物。每多一层二选一的分支,函数可能的执行路径就翻一倍。 六层嵌套的条件,理论路径是 2 的 6 次方——64 条。想靠测试覆盖这些路径,你的测试用例数量也要跟着指数增长。缩进每深一层,都是在给未来的自己签一张复利借条。

这就是视频标题里 "Shouldn't" 的真正分量:不该嵌套,不是审美洁癖,而是复杂度管理。

第三步:解法一——卫语句,让主流程活在第一层

视频给出的第一个手术方案是卫语句(Guard Clauses)+ 提前返回(Early Return):把“不满足条件该怎么办”放到函数开头直接处理掉,处理完立刻 return,让剩下 90% 的主逻辑全部平铺在第一层缩进里。

效果是阅读模式的根本转变:箭头代码要求读者“走进去再走出来”,卫语句版本只需要读者“在门口逐个检查安检口”,然后走一条笔直的快车道。原来那个深不见底的 else 大坑,被夷成了一排平铺的关卡。

第四步:解法二——提取函数,让嵌套块拥有名字

第二个方案是提取函数(Extract Function):当一块嵌套代码确实在做一件独立的事,就把它整体搬出去,起个说得清的名字。视频里有一个洞察很锋利:如果你需要靠一条注释来解释某块嵌套代码是干嘛的,那这块代码其实是在向你申请成为函数——名字就是那条注释的永久化。

提取之后的附带收益是抽象分层:主函数读起来像目录,被提取的函数像章节,每一层只用关心自己这一层的词汇。

第五步:升华——嵌套是“缺抽象”的症状

视频最后把话题抬高一格:很多时候你控制不了嵌套,是因为这段代码里根本还不存在那个正确的概念。你在 if 里反复判断某个对象的类型,可能是因为缺一个子类;你在循环里一边过滤一边聚合,可能是因为缺一个中间表示。嵌套在多数时候不是手滑,而是设计在报警:该出现的抽象还没被你命名出来。

知识与理论溯源:这套说法从哪来

视频没有给出参考文献,但它的每个论点在软件工程文献里都有扎实的出处。这一节把它们一一对号入座。

圈复杂度:1976 年就把账算清了

“路径数翻倍”的说法,学理源头是 Thomas J. McCabe 1976 年发表在 IEEE Transactions on Software Engineering 上的论文《A Complexity Measure》。McCabe 定义了圈复杂度(Cyclomatic Complexity):把函数的控制流图看成一个图,复杂度等于独立路径数,大致等于分支节点数加一。他基于大量工程经验给出了著名的经验阈值——圈复杂度 10 左右就值得警惕。此后四十多年,从索尼源(SonarSource)的静态分析工具到各类 CI 质量门禁,都是这套度量在不同程度上的商业化重演。G. Ann Campbell 后来在 SonarSource 的白皮书里提出“认知复杂度(Cognitive Complexity)”加以修正——嵌套本身要额外罚分,因为人类读代码的负担不是路径数,而是嵌套带来的上下文堆叠。这几乎就是这条视频论点的学术版。

单一出口教条:嵌套是怎么被“教”出来的

一个值得玩味的历史反转:很多老程序员对提前返回有生理性排斥,理由是“一个函数只能有一个出口”。这条规矩的祖师爷是结构化编程运动——1966 年 Böhm 和 Jacopini 证明了任何程序都能用顺序、分支、循环三种结构表达,1968 年 Dijkstra 那封《Go To Statement Considered Harmful》让“乱跳转有害”深入人心。但在传播过程中,这套理论被简化成了“单入口单出口(SESE)”的教条,几代教材由此把 return 唯一化,逼着所有错误处理一层层裹进 else 里——箭头代码有一半是这条教条的历史欠账。现代语言用实践完成了纠偏:Swift 直接把 guard 做成关键字(2015 年),Rust 在 1.65(2022 年)稳定了 let-else,Go 的官方风格更是满篇“先判错、先返回”。连 Linux 内核编码规范都白纸黑字地推荐用 goto 做集中清理,以换取主流程的平铺直叙。

箭头反模式的流行化:Coding Horror 与《重构》

“箭头代码”作为一个被命名的坏味道,流传最广的一波推手是 Jeff Atwood 2006 年的博客文章《Flattening Arrow Code》(把箭头代码压平),他给出的处方正是卫语句加提取函数。更早的规范化表述在 Martin Fowler 1999 年的《Refactoring》里,重构目录中有一条专门的条目:Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件)。而“Flat is better than nested”(平铺优于嵌套)作为设计哲学,早在 Tim Peters 1999 年写下的《Python 之禅》(后来的 PEP 20)里就刻进了整整一代人的肌肉记忆。

偶然复杂度 vs 本质复杂度

视频“嵌套是缺抽象的症状”这个收尾,暗合 Fred Brooks 1986 年经典论文《No Silver Bullet》里的区分:软件的困难分本质复杂度(问题本身固有的)与偶然复杂度(工具和写法强加的)。深层嵌套里的绝大多数,是偶然复杂度——它不该存在,存在只说明我们还没找到那个让代码变平的概念。John Ousterhout 在《A Philosophy of Software Design》(2018)里从正面给了同一种直觉一个名字:好模块是“深模块”(接口窄、实现厚),坏设计是“浅模块”——层层转发、层层判断,缩进就是浅设计的地形图。

批判性审查:哪些站得住,哪些要打折

这条视频的方向没有问题,但它是一支十分钟动画,简化在所难免。带着以下四点去看,你会吸收得更准。

第一,“提前返回”不是在所有语言里都免费。 在有 RAII、垃圾回收、defer、try-with-resources 的语言里,提前返回几乎无代价;但在裸 C 这类必须手工释放资源的语言里,每个提前返回点都是一处泄漏风险。Linux 内核选择“单一出口 + goto cleanup”不是守旧,而是在那个约束模型下的理性解。视频没有做这个区分,读者应记住:卫语句的代价曲线取决于资源管理模型。

第二,提取函数可以被滥用到另一种地狱。 Ousterhout 对“浅模块”的批评在这里同样适用:把一个函数切成十行只被调用一次的小函数,每个函数体和函数名一样长,读者被迫在十几个定义之间反复跳转——这是从“缩进地狱”搬进“跳转地狱”。提取的标准不是“代码块长”,而是“这一块是否构成一个值得命名的、独立于上下文的概念”。视频提到了命名,但没有展开这个更深的不确定性。

第三,圈复杂度阈值是经验值,不是法律。 McCabe 的 10 是四十多年前的工程启发式,后续研究对“复杂度指标与缺陷率的相关强度”结论并不一致。它作为报警器很有价值,作为 KPI 考核指标则会催生把一个 if 改写成查表、把五个条件塞进一个布尔表达式之类的应试行为。指标应该是体温计,不该是绩效单。

第四,有些嵌套是本质的,压不平。 矩阵的多重循环、树的递归遍历、组合枚举——这些嵌套表达的是问题本身的维度结构,强行“压平”只会更难读。视频的标题有标题党的成分:该反对的是条件性嵌套(错误处理、前置校验层层相套),而不是一切嵌套。分不清这一点的团队会把代码整理成另一场灾难。

总体而言:视频的核心论证(嵌套放大认知负担与测试成本、卫语句和提取函数是主要解法、深层嵌套常是设计缺陷的信号)在文献与实践中有充分支撑;被夸大的部分主要在表述的绝对化——"shouldn't" 应读作 “多数时候不值得”,而非 “一律禁止”。

认知提升与实用启示:三条可以明天就用

1. 把“账单”变成仪表盘,而不是靠自觉。 在 CI 里挂上深度和复杂度门禁:前端用 ESLint 的 max-depth(建议 3)与 complexity(建议 10);Python 用 radon 或 xenon;Go 用 gocyclo;Rust 用 Clippy 的 cognitive_complexity lint。阈值不必照抄教科书,但必须存在——没有度量的代码规范,最后都会退化成代码评审时的各抒己见。

2. 改写函数时,先写坏情况。 动手顺序本身就是防嵌套疫苗:拿到一个函数,先把所有“参数不对、查不到、没权限”的分支在开头用卫语句打发掉,让主流程成为你读到的第一个完整故事。判断一个函数是否健康有个粗糙但好用的体感测试:顺着第一层缩进读完,能不能听懂这个函数的一生。如果主线故事被 else 切得七零八落,就该动手了。

3. 提取函数时,用“能不能起一个不讲废话的名字”来验收。 名字起得出来,说明概念存在,提取成立;名字起不出来、只能叫 doSomething2,说明你还没找到那个抽象——这时候该回头改设计(补一个类型、一个中间表示、一个模块边界),而不是硬提一个函数或者加一条注释糊弄过去。命名难度是抽象质量最便宜的试纸。

视频论证结构图

flowchart TD
    A[深层嵌套代码<br/>箭头反模式] --> B[病根一<br/>错误处理混进主流程]
    A --> C[病根二<br/>单一出口的旧教条]
    B --> D[每层分支<br/>路径数翻倍]
    C --> D
    D --> E[圈复杂度爆炸<br/>难读 难测 难改]
    E --> F[嵌套其实是<br/>缺抽象的报警]
    F --> G[解法一<br/>卫语句提前返回]
    F --> H[解法二<br/>提取成命名函数]
    G --> I[主流程平铺在第一层]
    H --> J[每层一个抽象高度]
    I --> K[复杂度账单结清<br/>代码重新敢改了]
    J --> K

延伸阅读

  1. CodeAesthetic《Abstraction Can Make Your Code Worse》 — 本篇的天然续集:讲“提取抽象”本身的代价,正好补上本文批判性审查第二、四点没展开的部分,同一频道内检索即得。
  2. Jeff Atwood《Flattening Arrow Code》(Coding Horror, 2006)— “箭头代码”一词流传最广的出处,卫语句处方的博客原文:https://blog.codinghorror.com/flattening-arrow-code/

参考来源

  1. CodeAesthetic 频道(YouTube) — 视频 Why You Shouldn't Nest Your Code 的发布频道,标题、论点与频道信息来源
  2. Cyclomatic Complexity - Wikipedia — McCabe 1976 年《A Complexity Measure》及圈复杂度定义、阈值的背景综述
  3. PEP 20 – The Zen of Python — "Flat is better than nested" 的官方文本
  4. Flattening Arrow Code - Coding Horror — Jeff Atwood 2006 年对箭头反模式的经典论述

本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入