switch必须显式覆盖所有枚举值,禁用default代替穷尽性;每个case应调用职责分离的独立函数;结尾强制break防穿透;状态变量须严格绑定枚举类型并禁用隐式转换。

switch 必须覆盖所有枚举值,否则状态会漏处理
编译器不报错 ≠ 逻辑安全。Java、C#、TypeScript 等语言在 switch 中未覆盖全部 enum 值时,可能只警告或完全静默——但运行时一旦进入未处理的状态,就会掉进 default 分支,甚至直接跳过逻辑,导致状态卡死或行为异常。
正确做法是:显式列出每个枚举成员,拒绝用 default 代替穷尽性。例如订单状态 OrderStatus 有 CREATED、PAID、SHIPPED、DELIVERED、CANCELLED 五个值,switch 就得写满五次 case。
- Java 17+ 启用
-Xlint:fallthrough和-Xlint:switch可捕获遗漏 - TypeScript 开启
noImplicitReturns+strictNullChecks能在函数返回类型上暴露漏分支 - C# 8+ 的
switch表达式强制穷尽,缺一个就编译失败
case 里别塞太多逻辑,状态行为要拆成独立函数
把校验、I/O、异步等待、资源释放等操作全堆在 case 块里,会让主循环臃肿难读,也破坏单次状态更新的原子性。比如 case SHIPPED: 后面跟着发短信、调物流 API、更新 DB、发 MQ,一出错就难定位是哪步挂了。
更可控的方式是把每个状态的职责切分清楚:
-
onEnterShipped():发货前检查库存和运单号是否生成 -
updateShipped():轮询物流接口,超时自动标记为异常 -
shouldTransitionToDelivered():判断签收凭证是否已回传
主 switch 只负责调用这三个函数,并根据返回值决定下一步状态赋值,例如:if (shouldTransitionToDelivered()) current = OrderStatus.DELIVERED;
break 缺失会导致状态“穿透”,这是最隐蔽的 bug 来源
少一个 break,程序不会报错,但会顺序执行下一个 case 的代码。比如本该停在 PAUSED,却因漏写 break 而误入 STOPPED 分支,触发资源清理,用户 resume 时发现连接已断。
C++17 起可用 [[fallthrough]] 显式标注有意穿透,但状态机中几乎从不适用。真正该做的是:
- 所有
case结尾强制加break(除非你明确知道且文档化了穿透意图) - GCC/Clang 加
-Wimplicit-fallthrough让编译器提醒潜在穿透 - IDE 设置保存时自动补
break(如 IntelliJ 的 “Insert ‘break’ statement when inserting ‘case’”)
enum 类型必须与 switch 变量严格一致,避免隐式转换污染
用裸 enum(如 C 风格)或 int 代替 enum class / enum,会导致状态变量可被任意整数赋值,比如 current = 999; —— 编译器不拦,运行时才崩。
安全写法要求三重绑定:
- 状态变量声明为枚举类型:
OrderStatus current = OrderStatus.CREATED; -
switch表达式类型必须是该枚举,不能是int或Object - 所有赋值都通过枚举字面量,禁用数值强制转换(如 Java 不允许
(OrderStatus)2)
这能堵住非法状态注入,也让 IDE 在重命名枚举值时自动更新所有 case 引用,而不是留下一堆散落的魔法数字。
状态机真正的复杂点不在 switch 写法,而在于「状态迁移条件」的判定时机和副作用隔离——比如网络回调触发状态变更时,是否允许并发多次调用?是否需要加锁或去重?这些没法靠 case 语法解决,得靠状态跃迁函数本身的幂等性和边界控制。


















