有一种东西,一旦用过就回不去了。Alex Fedoseev 管它叫"软件设计的基石"。

他说的东西不新鲜——代数数据类型(ADT),尤其是和类型(sum type)。但他说清楚了一件事:这不是类型系统的学术体操,是日常编码的思维方式。
文章从一个简单的例子切入。一个网站的深色主题设置,如果用布尔值 + 可空字段来表示——boolean 表示是否可切换,nullable 的默认模式、浅色方案、深色方案——会产生大量无意义的组合。方案缺了、配置冲突了、全 null 了。要知道哪些组合有效,作者说,你得靠"文档、校验、或者部落知识"。
换个写法。Rust 的 enum,两个变体:Switchable { default_mode, light_scheme, dark_scheme } 和 Fixed { mode, scheme }。必填字段不会漏,互斥字段不会串。编译器拒绝一切胡来的配置。这就是"让非法状态不可表示"——把领域规则写进类型,而不是写在注释里。
然后他讲了一个更接地气的场景:parse_port。一个解析端口号的函数,在异常语言里,返回值是 number,但随时可能抛异常。你看到签名的一瞬间,完全不知道它会不会炸。在 Rust 里,返回值是 Result<u16, ParsePortError>,ParsePortError 自己又是一个和类型——OutOfRange { min, max } 和 NotANumber。调用的代码不处理 Result 就编译不过,错误不可能被偷偷吞掉。
他说这不是因为 Result 类型有多特别,TypeScript 也能写一个。关键是 Rust 的标准库和生态圈里,这是约定俗成的方式。异常语言里,只要有一个依赖方抛异常,整条调用链就失去了确定性。
文章后半段强调了模式匹配的穷尽性。给 Role 枚举加一个 Staff 变体,编译器会把所有 match Role 的地方标红——不是报错,是给你一份审查清单。作者说这叫"编译器引导变更",比 grep 搜索字符串和布尔值快,也比单元测试兜底可靠。
他反对抽象布尔辅助函数,比如到处写 is_admin 和 can_edit。这些函数把角色逻辑散落到代码库各处,编译器追踪不到。也反对 match 里的 _ => 通配分支——新增一个变体时,通配分支会沉默地吞掉它,用旧逻辑处理新情况,编译器不吭声。
Reddit 评论区有人补充了更早的渊源。有人指出 Yaron Minsky 在 OCaml 社区早就讲过"make illegal states unrepresentable"。还有人提到 Alexis King 的经典文章 "Parse, Don't Validate",主张在边界处就把外部数据转成领域类型,而不是在内部反复校验。有人用 SQL 注入举例:String 进来,Username 出去,单引号转义在转换时一次性完成,后面所有代码看到 Username 就知道它已经安全了——即使你周五深夜加班,Bobby Tables 也伤不到你。
Alex 最后说,用过和类型和穷尽匹配之后,他没法把它们当"可选的额外功能"。这是他选编程语言的基线。
参考来源: