null必须前置拦截,default是明确兜底策略而非容错;switch不处理null,需显式判空后提前返回;default必须定义具体行为,禁止空日志;优先用enum等强类型收窄输入;借助编译器警告、静态检查工具强制覆盖。

核心原则是:null 必须前置拦截,default 不等于容错,而是明确兜底策略的执行点。Java、C# 等语言中 switch 对 null 零容忍,一旦传入直接抛 NullPointerException,根本不会进入任何 case 或 default;而非法值(如未知字符串、越界整数)若未被 case 覆盖又缺 default,则静默跳过,埋下逻辑漏洞。
第一步:永远先校验 null,不交给 switch 处理
switch 表达式本身不处理 null —— 它连入口都进不去。所有引用类型(String、包装类、自定义对象)在进入 switch 前必须显式判空:
- 用
Objects.nonNull(expr)或expr != null提前守门 - 为空时直接返回错误响应、抛
IllegalArgumentException或走统一异常处理器 - 别写成
if (expr == null) { ... } else { switch(expr) { ... } }这种嵌套,可读性差;推荐提前 return
第二步:default 分支不是“可有可无”,而是必须明确行为
default 不是补漏工具,它是你对“未知输入”的主动承诺。它的存在与否、行为设计,直接决定系统是否健壮:
- 外部输入(HTTP 参数、JSON 字段、用户命令)→ 记录完整上下文 + 返回 400 或抛业务异常
- 内部状态机(订单状态、审批流程)→ 抛
IllegalStateException("Unexpected state: " + value),让问题在测试环境暴露 - 协议字段或未来可扩展枚举 → 上报监控指标 + 调用
handleUnknownField(),而非仅打日志 - 禁止写成
default: log.warn("should not happen");—— 这等于放弃防御
第三步:用类型系统收窄输入范围,从源头减少非法值
很多“非法输入”其实是设计阶段放任字符串/整数泛滥导致的。越早用强类型约束,switch 就越安全:
- Java:用
enum Status { ACTIVE, INACTIVE }替代String status;配合values()和单元测试确保所有枚举都被 case 覆盖 - Go:定义
type Status int+const (Pending Status = iota; Approved; Rejected),并在 switch 前加if !s.IsValid() { ... } - 即使 JS/C++ 等弱类型语言,也应在函数入口用
assert(value ∈ KNOWN_SET)做运行时断言
第四步:借助工具和语言特性强制检查
别依赖人眼检查。不同语言有不同保障手段:
- Java 14+:优先使用 switch 表达式(
switch (v) { case "A" -> ...; default -> ...; }),编译器强制穷尽(但仅限常量表达式) - 启用编译警告:
-Xlint:switch(提示 missing default)、-Xlint:fallthrough(提示漏 break) - Go:用
staticcheck检测未覆盖的枚举分支;Rust 直接编译报错 - CI 阶段接入 SonarQube 或 ESLint 插件,拦截 CWE-478(Missing Default Case)类缺陷

















