码农高天驳斥入参免检论 称入口前置防御可阻断系统崩溃与长链调试

写程序为什么要检查传入值?给编程新手们一个详尽的解答 — 码农高天

码农高天

函数入参校验并非默认程序存在逻辑缺陷,而是为了在独立模块入口落实及早报错原则,避免非法输入深陷调用链路并引发高成本的逆向排错与生产事故。

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)

中文简报

码农高天驳斥入参免检论 称入口前置防御可阻断系统崩溃与长链调试 函数入参校验并非默认程序存在逻辑缺陷,而是为了在独立模块入口落实及早报错原则,避免非法输入深陷调用链路并引发高成本的逆向排错与生产事故。 视频首先纠正了初学者“设计正确就不会传入错值”的误区,指出真实系统必须接收外部不可控输入,且大型工程依赖模块化与防御性编程来提升鲁棒性。随后,高天将函数面临的非预期输入细分为 Assert、Exception、Invalid 和 Special 四类,阐明生产环境中绝大多数入参检查并非单纯挑错,而是为了优雅降级或处理数学与业务边界奇异点。最后,视频强调“尽早报错”原则恰恰要求在函数入口即刻拦截非法参数,否则深层调用中暴露的底层异常将导致排错与回溯成本呈指数级上升。 针对新手“只要设计没问题就不会传错值”的误区,高天指出程序无法假设外部输入合法,必须通过模块化拆分与入参防御来保证整体系统的鲁棒性。 (0:00) 函数入参检查并不等同于捕获错误,而是处理主干逻辑无法直接应对的特殊值,涵盖 Assert、Exception、Invalid 与 Special 四类主流场景。 (2:35) 生产环境代码通常以兜底(Invalid)与特殊分支规避(Special)为主流,防止系统因非预期输入直接崩溃,这与单机运行的自用脚本完全不同。 (2:35) 入口处做参数校验正是践行“尽早报错”原则,避免错误沿调用链传递到末端导致底层报错,从而大幅降低逆向排查与 Debug 回溯成本。 (5:28)

Reading summary

针对初学者“逻辑无误就不需检查入参”的疑问,码农高天指出,参数校验不仅是抵御不可控外部输入的防线,更是保障模块化解耦、分流业务边界以及降低调试成本的核心工程实践。 不可信输入与模块解耦要求 (0:00) 初学者常假设程序设计无误便不会传入非法参数,但高天强调该假设在实际开发中并不成立。一方面,诸如网站后端等系统必须直接面对千奇百怪的外部用户输入,绝不可能预设数据完全合法;另一方面,即便代码仅供个人使用,软件工程也要求系统保持模块化,而非构建高度紧耦合、依赖每个环节微米级运转的脆弱整体。开发者在编写独立模块时,应将调用方视为不可信任的外部实体,赋予每个模块独立处理异常输入的能力,从而提升整体系统的鲁棒性(robustness)。 四类特殊输入与生产环境处理 (2:35) 高天澄清,检查参数并不等同于寻找“错误”,而是定位主干逻辑无法直接处理的特殊值。他将这些输入划分为四类:一是内部类私有函数中的“Assert”,作为开发者自检的健全性检查(Sanity Check),在生产环境中常可省略;二是公开接口无法处理时抛出 ValueError 或 TypeError 的“Exception”;三是协议允许的非预期输入“Invalid”,通过返回 None 等方式优雅降级;四是业务合法但处于数学奇异点的“Special”,例如空列表求均值若套用常规公式会因分母为 0 崩溃,需单独分支兜底。高天指出,Invalid 与 Special 才是生产环境的主流场景,前置检查旨在分流并防止系统崩溃,与单机运行的脚本有本质区别。 落实尽早报错以降低排错成本 (5:28) 对于初学者主张“出错应尽早暴露以便排查”的观点,高天表示认同,但指出初学者的推论方向恰恰颠倒:正是为了实现尽早报错,才必须在函数入口处进行校验。若不作前置检查,以空列表求均值直接除零为例,系统抛出 ZeroDivisionError 后,开发者必须反推除数来源才能发现入参为空;若在入口处直接校验并主动报错,则能立即提供清晰的上下文信息。在调用链极深或规模庞大的项目中,未经校验的参数经多层流转后在底层引发异常会导致高昂的逆向排查成本,入口校验能确保报错紧邻错误源头。 参数检查不仅用于阻断错误,其更广泛的职责在于支撑模块化防御、引导业务边界分支,并在必要时将故障限制在离源头最近的入口处,从而保障系统的整体稳定性与可维护性。

Source quotes

[5:28] 码农高天: 正是因为希望程序早点出错我们才要去检查参数的合法性 [5:28] 码农高天: 如果有错误,应该尽量早报错报错的位置离你的错误越近越好这就是为什么编译期错误要好于运行时错误为什么我们需要所谓的static type checker [0:00] 码农高天: 你应该把你的程序拆分成若干个模块而每个模块都有能力去处理所谓错误的值以此增加你程序的鲁棒性 也就是robustness

Original transcript

[0:00] 最近呢有一位朋友在我的粉丝群问了一个问题啊他说作为一个编程初学者,他最近在思考检查传入值是否为空在运行这种应不应该写。他给出的疑惑呢是如果程序设计的没有问题就不会传入错误的纸如果传入了错误的值呢就应该早点出错好寻找问题所以他问说这种判断是不是多余的那我相信肯定有不止一位刚接触编程的朋友会有这种想法所以我们今天就来尝试解释一下为什么要去判断一个参数的合法性首先他提到说如果程序设计的没有问题就不会传入错误的值这个说法明显是有问题的对吧因为你的程序很有可能不止你一个人在用我们举一个很常规的例子 你写了一个网站的后端 你肯定要去处理用户输入的数据对吧那只要涉及到用户用户给的信息很有可能就什么玩意都有了所以但凡你的程序需要接受其他的用户输入你就没有办法保证你传入参数的合法性好那你说如果我写的这块程序只有我自己用没有任何其他的用户呢即便是这样,从封装的角度说,你也要去做传入值的判断因为在我们写比较大的程序的时候我们一定要有模块化的概念你不能说我写的所有的这些程序都是一个整体对吧他们需要每一个环节都非常精密的运行他才能工作这种依赖性太高的程序是非常难以处理的所以当你在写每一个模块的时候,你都应该认为所有的传入值都是其他用户可能给你的传入值,即使使用这一段程序的只有你自己。你会发现把若干个比较稳定的模块拼起来是比写一大坨程序要简单的也就是说你应该把你的程序拆分成若干个模块而每个模块都有能力去处理所谓错误的值以此增加你程序的鲁棒性 也就是robustness所以第一点千万不要觉得如果程序设计的没问题就不会传入错误的值第二点呢是对错误的值这个错误的理解他提到说如果传入了错误的值就应该早点出错但是当我们对传入数据进行判空或者是其他判断的时候,它不一定在寻找错误的值。对传入参数进行判断只是在寻找我们这段函数不能以正常逻辑处理的特殊值但这个值未必就是错的我们可以粗略的把特殊值分成下面几个种类AssertException, Invalid跟 Special我们来逐一解释一下比如说我们写了一个类这个类里面有一个私有函数 求一个列表的平均值由于这个函数是私有的所以我们可以认为这个传入值它必然是一个list如果不是list 那一定是我程序写错了所以在这里为了所谓的 sanity check,我们可以写一个 assert is instance list list那这种情况就属于他刚才提到的,如果程序设计的没问题,就不会传入错误的值所以对于这种assert的情况 我们确实经常会不写它第二种叫做Exception这种情况就是你给我输入值我是真不知道怎么处理我能做的就只是给你报一个异常和刚才同样功能的函数,如果它是一个public method的话,这里我们用 exception 就更加合适因为你不知道用户传进来是什么东西但是如果用户传进来的不是一个list呢你确实拿它没什么办法这种情况下早点报一个异常告诉用户说你传出来的东西肯定错了是比较合适的第三种情况叫invalid就是这个输入值不是我想要的但是我有能力去处理它我不会抛一个异常 但是我会通过某种方式告诉调用我的人 这块让你数据给错了比如当我这个公开的对外接口list average明确的告诉你了 如果你的输入是错误的 我会返回一个num的时候这里我就可以不跑异常而直接返回一个难这也是一种特殊数据的处理方式包括比如我们这里还可以判一下list是不是为空如果list为空也反回暖最后一种情况我们叫做special就是这个输入值啊它是一个合法值但是我们没有办法通过我们正常的逻辑去计算它比如说 如果我们认为一个空的list [5:00] 只是0的话那输入一个空 list 就是一个合法值但是我们正常求平均的时候是用 sum 除以 length这里的Length不可以是0于是我们前面就加了一个判断如果这个list是空的话返回0那我们可以很明确的看到对于invalid跟special这两种情况我们不希望程序报个错我们不希望程序出错那在真正的production代码中,这种情况才是主流我们不希望我们部署好的代码动不动就报个错然后程序崩溃了这和我们自己在本机上运行的脚本是不一样的我们希望它可以正常的处理所有的输入你程序起码别崩溃了你想想如果你使用的一个网站的后端遇到用户一个不寻常的输入,他这网站后端就直接崩溃了,就报错了,你受得了吗?所以很多时候对输入值的判空或者其他特殊的判断是为了确定接下来应该怎么处理它是为了防止你的程序报错的好 最后我们再说一点 他这里提到了 如果传入的只是错误了 就应该早点报错这个概念本身是正确的如果有错误,应该尽量早报错报错的位置离你的错误越近越好这就是为什么编译期错误要好于运行时错误为什么我们需要所谓的static type checker但是正是因为我们希望尽早的报错我们才需要去检查这个值是不是合法我们还用刚才那个函数举例子如果我们不进行判空,直接sum list除以lens list当我们传入的值是一个空 list的时候可以看到他报的错是Zero Division Error,他说你这个除数不能是0你从除数不能是零 领悟到这个list是一个空list是不是需要几步你要先看到除法的除数是lens list然后再反应过来这个list可能是空list但如果我们在最开始进行判断并且直接race一个value error的话你可以把报错的信息写得尽量详细,让你看一眼就知道哪里出错了,是什么样的错误。或许在这个例子中大家感觉这两件事差的不是太多但是我们可以想象一下在比较复杂的函数里我们可能经过了很多行代码才使用到这个非法的传入值甚至有的时候,这个非法的传入值已经被进行了若干词变换了然后他在这段函数很后面才给你报一个错你需要经过大量的回溯推演才能知道这块我的程序为什么报错而在刚开始就检查这个参数的合法性会让你debug变得更加容易所以正是因为希望程序早点出错我们才要去检查参数的合法性好 那这期视频就到这里了 希望对刚学编程的朋友有所帮助如果喜欢的话 记得点一下关注 别忘了意见三连哦