近日,一篇题为《Parse, Don’t Validate – In a Language That Doesn’t Want You To》的技术文章在开发者社区引发热议。该文由资深程序员撰写,直指编程界长期存在的一个思维误区:过度依赖验证(validation)而忽视真正的解析(parsing)。尤为讽刺的是,作者指出,在某些主流语言的设计哲学中,开发者往往“被引导”走向验证而非解析——即便解析才是更安全、更优雅的解决方案。

什么是“解析”与“验证”?

简单来说,验证是检查数据是否符合预期,如果不符合则抛出错误或返回失败;而解析则是将输入数据转化为更有意义的内部表示,同时确保合法数据被正确解释,非法数据则无法通过类型系统或结构约束进入核心逻辑。

以处理一个日期字符串为例:验证的做法是先用正则表达式匹配格式,再检查月份范围、日期范围等,最后返回一个布尔值或字符串。解析的做法则是直接定义一个“日期”类型,将字符串转化为该类型的实例,任何不合法的输入在转换过程中就被拒绝,后续代码只需处理类型安全的日期对象。

“验证把责任推给了调用方——‘你得自己处理错误’;解析则把困难留给自己,把安全留给下游。”作者在文中写道。

语言为何“不让你这么做”?

文章的核心批判对象是那些缺乏代数数据类型(如联合类型、模式匹配)或类型系统不够强大的语言——例如传统JavaScript、老版本Python、以及早期Java。在这些语言中,开发者难以自然地构建“要么成功并返回结果,要么失败并携带原因”的显式结构。

“当你只能用null表示失败,或者用异常表示所有错误时,你就会被推回验证的旧路:先检查,再假设一切正常,最后祈祷。”作者分析道。他举例,在C语言中,函数常返回错误码,用户必须记得手动检查;而现代Rust或Haskell中的Result类型则天然支持解析——调用者被迫处理所有可能性。

真实世界中的案例

文章引用了一个实际项目中的教训:一个网络协议解析器原本用大量if-else验证输入长度和校验和,结果在边界情况下多次崩溃。重构为基于解析器的设计后,每个字段都被视为独立的类型,整个流程变成“读取 -> 解析 -> 组合”,非法数据在入口处就被阻止。“验证让你编写‘防御性代码’,解析让你编写‘无懈可击的代码’。”

另一个案例来自前端表单验证:许多团队在提交前运行数十个验证函数,但当数据到达后端时,仍需重复验证。如果采用“解析即验证”的思路,前端直接构造一个强类型请求体,后端只需接受该类型,错误早在输入阶段就被捕获。

行业反思:从“防错”到“不容错”

文章引发了开发者对“防御性编程”的重新审视。有评论指出,验证本质上假设“我信任你,但我要检查你”;解析则假设“我不相信你,直到你按我的规则变成可信的数据”。后者的哲学更符合现代安全编程理念。

不过,作者也承认,并非所有语言都提供了解析的便利工具。他建议,如果被迫在“验证友好”的语言中工作,可以尝试借助外部库(如TypeScript中的zod、Rust中的nom、Haskell中的attoparsec)来模拟解析器模式。更重要的是,改变思维:“别想着‘检查输入是否合法’,而要想着‘把输入转化为合法形式,否则拒绝接受’。”

结语

《Parse, Don’t Validate》并非新概念,但将其放在“语言设计阻碍”的背景下重新讨论,击中了许多开发者的痛点。当一门语言不鼓励你做正确的事时,你更应该主动选择正确的方法。毕竟,解析不是一种奢侈,而是一种责任——对所有可能流入系统的数据说:“要么按我的规矩进来,要么别进来。”