码农高天
函数入参校验并非默认程序存在逻辑缺陷,而是为了在独立模块入口落实及早报错原则,避免非法输入深陷调用链路并引发高成本的逆向排错与生产事故。
AI-assisted summary of 写程序为什么要检查传入值?给编程新手们一个详尽的解答 — original source. Check the original for full context.
Source published:
Key ideas
- 针对新手“只要设计没问题就不会传错值”的误区,高天指出程序无法假设外部输入合法,必须通过模块化拆分与入参防御来保证整体系统的鲁棒性。 (0:00)
- 函数入参检查并不等同于捕获错误,而是处理主干逻辑无法直接应对的特殊值,涵盖 Assert、Exception、Invalid 与 Special 四类主流场景。 (2:35)
- 生产环境代码通常以兜底(Invalid)与特殊分支规避(Special)为主流,防止系统因非预期输入直接崩溃,这与单机运行的自用脚本完全不同。 (2:35)
- 入口处做参数校验正是践行“尽早报错”原则,避免错误沿调用链传递到末端导致底层报错,从而大幅降低逆向排查与 Debug 回溯成本。 (5:28)
Detailed notes
## Background 该视频由 YouTube 频道“码农高天”于 2024 年 5 月 10 日发布。视频源于粉丝群里一位编程新手提出的疑问——既然程序设计得当就不该传入错误值,那么在函数入口检查传入参数是否多余;高天借此深入探讨了在函数中检查传入值的核心原因与工程意义。
## TL;DR 视频首先纠正了初学者“设计正确就不会传入错值”的误区,指出真实系统必须接收外部不可控输入,且大型工程依赖模块化与防御性编程来提升鲁棒性。随后,高天将函数面临的非预期输入细分为 Assert、Exception、Invalid 和 Special 四类,阐明生产环境中绝大多数入参检查并非单纯挑错,而是为了优雅降级或处理数学与业务边界奇异点。最后,视频强调“尽早报错”原则恰恰要求在函数入口即刻拦截非法参数,否则深层调用中暴露的底层异常将导致排错与回溯成本呈指数级上升。
## Atmosphere & style 高天的讲解风格清晰透彻、循循善诱,宛如一位经验丰富且极具耐心的资深工程导师在做一对一代码审查。面对初学者容易钻牛角尖的直觉偏误,他没有直接否定,而是从软件工程的底层逻辑与实际生产痛点切入,将抽象概念拆解得既具象又接地气。整段分享逻辑层层递进、推导严密,让观众在厘清思维误区的同时,切身体会到工业级代码与玩具脚本之间的认知鸿沟。
## Bulletin - [00:00](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=0s) 针对新手“只要设计没问题就不会传错值”的误区,高天指出程序无法假设外部输入合法,必须通过模块化拆分与入参防御来保证整体系统的鲁棒性。 - [02:35](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=155s) 函数入参检查并不等同于捕获错误,而是处理主干逻辑无法直接应对的特殊值,涵盖 Assert、Exception、Invalid 与 Special 四类主流场景。 - [02:35](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=155s) 生产环境代码通常以兜底(Invalid)与特殊分支规避(Special)为主流,防止系统因非预期输入直接崩溃,这与单机运行的自用脚本完全不同。 - [05:28](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=328s) 入口处做参数校验正是践行“尽早报错”原则,避免错误沿调用链传递到末端导致底层报错,从而大幅降低逆向排查与 Debug 回溯成本。
## Key takeaways - [00:00](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=0s) 初学者常误以为只要程序设计没问题就不会传入错误的值,但面向真实用户或外部系统的程序绝不可能假设入参始终合法。 - [00:00](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=0s) 即使是个人使用的代码,也应将程序拆分成具备错误处理能力的独立模块,模块化拼装能显著提升系统的鲁棒性。 - [00:00](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=0s) 在编写独立模块时,应将调用方视为不可信任的外部环境,确保模块自身具备自洽的防御能力。 - [02:35](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=155s) 参数校验的核心并非单纯为了抓错,而是为了识别并处理主干计算逻辑无法直接消化的特殊输入。 - [02:35](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=155s) 内部私有方法可以使用 Assert 进行健全性检查(Sanity Check),此类情况在生产代码中甚至可省略。 - [02:35](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=155s) 对外暴露的公开接口遇到无法处理的非法入参时,必须主动抛出如 ValueError 等明确异常告知调用方。 - [02:35](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=155s) 遇到非期望但属于边界情况的输入(Invalid),可以通过优雅降级返回约定标识(如 None)来兜底,避免系统崩溃。 - [02:35](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=155s) 输入合法但处于数学奇异点的情况(Special,如空列表求平均值分母为 0),需要通过前置分支规避运行时异常。 - [02:35](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=155s) 真实生产环境与本地脚本不同,核心诉求是维持服务稳定,Invalid 和 Special 这类优雅兜底与规避才是最主流的处理方式。 - [05:28](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=328s) 报错的位置距离出错的源头越近越好,这也是静态类型检查器和编译期错误优于运行时错误的原因。 - [05:28](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=328s) 未做入口校验会导致程序在执行到深层运算时才崩溃(如抛出 ZeroDivisionError),迫使开发者进行繁琐的逆向推演。 - [05:28](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=328s) 在复杂工程中,非法参数经多次传递后在底层报错会极大增加 Debug 成本,入口处的主动校验正是为了实现“尽早报错”。
## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=0s) 为什么要检查传入值与模块化封装
粉丝群里一位编程初学者提出了一个常见的困惑:在函数运行前检查传入值是否为空等操作到底有没有必要?提问者的理由是,“如果程序设计得没有问题,就不会传入错误的值;如果真的传入了错误的值,就应该早点出错以便寻找问题”,因此怀疑这种提前判断是不是多余的。码农高天指出,这种想法在刚接触编程的新手中相当普遍,但“如果程序设计的没有问题就不会传入错误的值这个说法明显是有问题的”。
首先,程序往往不止开发者自己一个人在使用。以最典型的网站后端开发为例,后端必须接收并处理前端用户输入的数据。一旦涉及到真实用户,输入的内容往往千奇百怪、无法预料。因此,只要程序需要接收外部用户输入,开发者就绝不可能假设传入参数永远合法。退一步讲,哪怕整套代码完完全全是写给自己一个人用、没有任何外部用户访问,从软件工程和代码封装的角度来看,参数校验依然不可或缺。大型程序必须具备模块化思想,不能把所有代码写成一个紧密耦合的庞大整体,要求每个环节都以微米级的精度毫无偏差地运转。高天强调:“你会发现把若干个比较稳定的模块拼起来是比写一大坨程序要简单的也就是说你应该把你的程序拆分成若干个模块而每个模块都有能力去处理所谓错误的值以此增加你程序的鲁棒性 也就是robustness”。当开发者编写任何一个独立模块时,都应该把调用方视为不可信任的外部用户,确保模块自身的独立健壮。
```mermaid flowchart TD accTitle: 模块化防御性编程模型 accDescr: 展示将整体代码拆分为具备入参校验的独立模块以提高鲁棒性 subgraph Bad["高度紧耦合设计 (脆弱)"] A["单一代码块 / 强假设入参合法"] --> B["某个环节数据异常"] --> C["整套系统崩溃"] end subgraph Good["模块化设计 (高鲁棒性)"] D["调用方 / 用户输入"] --> E["独立模块 A (入参校验 & 防御)"] E --> F["独立模块 B (入参校验 & 防御)"] end ```
---
[02:35](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=155s) 特殊输入值的四种分类:Assert、Exception、Invalid 与 Special
初学者对“错误的值”这一概念往往存在认知偏差:对传入参数做判空或校验,并不等同于在寻找“错误”的值,而是在定位那些“当前函数无法以常规主干逻辑直接处理的特殊值”。这些特殊值本身并不一定就是错的。为了让概念更清晰,高天将这些特殊输入粗略归纳为四大类别:
| 类别 | 典型场景 | 处理机制 | 核心目的与逻辑 | |:--- |:--- |:--- |:--- | | **Assert** | 类内部的私有函数(如内部辅助方法计算列表均值) | `assert isinstance(lst, list)` | 属于内部 Sanity Check(健全性检查)。因为是内部调用,开发者能保证只要逻辑没 Bug 入参必为合法;如果不对说明代码写炸了。生产中此类断言经常选择不写。 | | **Exception** | 暴露给外部调用的公开接口(Public Method)且无法继续处理 | 抛出异常(如 `ValueError`、`TypeError`) | 外部传入了完全无法解析或处理的类型,函数无能为力,必须以明确的异常告知调用者传参错误。 | | **Invalid** | 公开接口遇到非期望输入,但属于协议允许的边界情况 | 优雅降级返回特定标识(如返回 `None`) | 虽然输入不在理想范围内,但函数有能力妥善兜底,通过约定的返回值提醒调用方,而不是粗暴抛异常导致崩溃。 | | **Special** | 输入值完全合法,但落在正常计算公式的边界奇异点 | 前置分支处理(如空列表均值直接返回 `0`) | 业务上合法,但通用数学逻辑(如 `sum / len`,分母为 0)会触发运行错误,必须单独立逻辑规避。 |
以计算列表平均值的函数为例:如果是类内部私有方法,入参理应百分之百是 `list`,此时写断言只是为了开发时的防御性自检;但如果该方法是一个公开接口,用户可能塞入任何对象,抛出清晰的异常才是合情合理的做法。而对于 `Invalid` 与 `Special`,比如空列表求均值,如果业务规则认为空列表均值即为 0,那空列表就是一个完全合法的输入。但若直接套用 `sum(lst) / len(lst)`,就会遭遇分母为 0 的崩溃。高天指出,在真实的生产环境代码中,`Invalid` 和 `Special` 才是最主流的场景:“在真正的production代码中,这种情况才是主流我们不希望我们部署好的代码动不动就报个错然后程序崩溃了这和我们自己在本机上运行的脚本是不一样的”。网站后端绝不能因为用户的一个特殊输入直接抛错挂掉,前置的参数检查正为了引导程序走入正确的处理分支,防止整个系统崩溃。
---
[05:28](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=328s) “尽早报错”原则与降低 Debug 回溯成本
针对提问者提到的“如果传入了错误的值就应该早点出错以便寻找问题”,高天肯定了这个原则的正确性:“如果有错误,应该尽量早报错报错的位置离你的错误越近越好这就是为什么编译期错误要好于运行时错误为什么我们需要所谓的static type checker”。然而,初学者的推论方向恰恰反了——正是因为我们希望尽早报错,才必须在函数入口处对参数合法性做严格检查。
如果不对传入值做前置校验,以列表求平均值传入空列表为例,程序会一直执行到除法运算,然后抛出一个 `ZeroDivisionError: division by zero`。开发者如果看到这个报错,需要在大脑中进行多层推演: - 看到除数为 0; - 去看代码发现除数是 `len(lst)`; - 进而才反推出来是因为传入的 `lst` 为空列表。
如果在函数入口处直接校验并主动 `raise ValueError`,开发者就可以直接附带清晰明确的报错提示信息,让人一眼看清到底是什么参数在什么地方不符合规范。在只有两三行代码的简单示例里两者的差距或许并不显著,但在动辄几百行甚至调用链极深的大型工程中,一个非法的传入参数往往会经历多次赋值、类型转换或在多个子函数间传递。如果直到代码末尾才由底层的无关操作抛出一个莫名其妙的运行时异常,开发者将不得不耗费巨大的精力去进行逆向回溯与断点排查。入口处的参数检查让报错发生离源头最近的地方,极大降低了排错成本;因此,“正是因为希望程序早点出错我们才要去检查参数的合法性”。
## Notable quotes > [05:28](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=328s) 正是因为希望程序早点出错我们才要去检查参数的合法性 — 码农高天
> [05:28](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=328s) 如果有错误,应该尽量早报错报错的位置离你的错误越近越好这就是为什么编译期错误要好于运行时错误为什么我们需要所谓的static type checker — 码农高天
> [00:00](https://www.youtube.com/watch?v=vbM9gxLrYK4&t=0s) 你应该把你的程序拆分成若干个模块而每个模块都有能力去处理所谓错误的值以此增加你程序的鲁棒性 也就是robustness — 码农高天
## Mentioned 码农高天, Python
## Video description 前两天在粉丝群有个朋友问了一个看似简单的问题,但是这个问题之后其实也有蛮多可聊的内容。这期视频我们就尽可能详细地聊一下,为什么我们在函数里要检查传入值。
[Source](https://www.youtube.com/watch?v=vbM9gxLrYK4)