AI Library · 知识库

# 处理不同状态:穷尽检查与模式匹配

学习目标:掌握代数状态消费中的模式解构(Pattern Matching)、类型收窄(Narrowing)与编译期穷尽检查(Exhaustiveness Checking)。

前置知识与范围:有限状态建模。聚焦如何安全消费互斥状态,并保证新增状态时编译器主动拦截漏写分支。

# 问题与边界

针对任务状态执行调度动作(返回下一步动作指令字符串):

  1. 若为 Pending(retries):
    • 带有守卫条件:若 retries < 3 返回 "retry";若 retries >= 3 返回 "abandon"。
  2. 若为 Success(result):返回 "save: " + result。
  3. 若为 Failed(code, message):返回 "notify: " + message。

核心检验标准:

# 关键差异

语言 模式匹配语法 守卫条件(Guard) 穷尽检查(Exhaustiveness)保障方式
Rust match state { ... } if retries < 3 => ... 编译器原生强制,漏写枚举变体直接编译失败
Go switch s := state.(type) 分支内手写 if 编译器不检查穷尽,漏写分支默认静默跳过
Java Java 21 switch (state) 模式匹配 when r < 3 对 sealed 体系编译器原生强制穷尽检查
JavaScript switch (state.status) 分支内常规 if 仅能在 default 中抛出运行时异常
TypeScript switch (state.status) 分支内 if 在 default 中使用 never 类型守卫,编译期拦截
Kotlin when (state) 分支内手写 if 对 sealed 体系省略 else 触发强制穷尽检查
Python Python 3.10 match state: case Pending(r) if r < 3: 使用 typing.assert_never 配合 mypy 静态检查
C++ std::visit(overloaded{...}, var) lambda 内部条件 std::visit 要求重载集合必须穷尽所有 variant 类型

# 各语言实现

# Rust

Rustenum TaskState {
    Pending { retries: u32 },
    Success(String),
    Failed { code: i32, message: String },
}

fn next_action(state: &TaskState) -> String {
    match state {
        // 模式守卫(Match Guard)
        TaskState::Pending { retries } if *retries < 3 => "retry".to_string(),
        TaskState::Pending { .. } => "abandon".to_string(),
        TaskState::Success(res) => format!("save: {res}"),
        TaskState::Failed { message, .. } => format!("notify: {message}"),
    }
}

fn main() {
    assert_eq!(next_action(&TaskState::Pending { retries: 1 }), "retry");
    assert_eq!(next_action(&TaskState::Pending { retries: 3 }), "abandon");
    assert_eq!(next_action(&TaskState::Success("data_v1".to_string())), "save: data_v1");
    assert_eq!(next_action(&TaskState::Failed { code: 500, message: "disk full".to_string() }), "notify: disk full");

    println!("action: {}", next_action(&TaskState::Pending { retries: 1 }));
}

Rust 编译器具备严格的代数模式分析能力;删除任何一个非守卫分支,编译器均会立即报错拒绝生成二进制。

# Go

Gopackage main

import "fmt"

type TaskState interface {
	isTaskState()
}

type Pending struct{ Retries int }
func (Pending) isTaskState() {}

type Success struct{ Result string }
func (Success) isTaskState() {}

type Failed struct {
	Code    int
	Message string
}
func (Failed) isTaskState() {}

func NextAction(state TaskState) string {
	switch s := state.(type) {
	case Pending:
		if s.Retries < 3 {
			return "retry"
		}
		return "abandon"
	case Success:
		return "save: " + s.Result
	case Failed:
		return "notify: " + s.Message
	default:
		panic("unhandled state") // Go 必须依靠运行时防御
	}
}

func main() {
	if NextAction(Pending{Retries: 1}) != "retry" {
		panic("mismatch")
	}
	if NextAction(Success{Result: "data_v1"}) != "save: data_v1" {
		panic("mismatch")
	}

	fmt.Printf("action: %s\n", NextAction(Pending{Retries: 1}))
}

Go 的 switch .(type) 无法在编译期强制要求覆盖所有接口实现,生产实践中建议在 default 显式 panic 防御。

# Java

Javapublic class Main {
    public sealed interface TaskState permits Pending, Success, Failed {}
    public record Pending(int retries) implements TaskState {}
    public record Success(String result) implements TaskState {}
    public record Failed(int code, String message) implements TaskState {}

    public static String nextAction(TaskState state) {
        // Java 21 switch 模式匹配 + when 守卫
        return switch (state) {
            case Pending p when p.retries() < 3 -> "retry";
            case Pending p -> "abandon";
            case Success s -> "save: " + s.result();
            case Failed f -> "notify: " + f.message();
        }; // 密封体系无需 default,编译器强制穷尽检查!
    }

    public static void main(String[] args) {
        assert "retry".equals(nextAction(new Pending(1)));
        assert "abandon".equals(nextAction(new Pending(3)));
        assert "save: data_v1".equals(nextAction(new Success("data_v1")));

        System.out.println("action: " + nextAction(new Pending(1)));
    }
}

Java 21 的模式匹配表达式对于 sealed 类自动触发穷尽校验,若增加子类而未更新 switch,编译直接报错。

# JavaScript

JavaScriptconst assert = {
  equal: (a, b) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a, b) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn, errType) => {
    try { fn(); throw new Error("expected throw"); } catch (e) {
      if (e.message === "expected throw") throw e;
      if (errType && !(e instanceof errType)) throw e;
    }
  }
};

function nextAction(state) {
  switch (state.status) {
    case "pending":
      return state.retries < 3 ? "retry" : "abandon";
    case "success":
      return `save: ${state.result}`;
    case "failed":
      return `notify: ${state.message}`;
    default:
      throw new Error(`Unhandled state: ${JSON.stringify(state)}`);
  }
}

assert.equal(nextAction({ status: "pending", retries: 1 }), "retry");
assert.equal(nextAction({ status: "pending", retries: 3 }), "abandon");
assert.equal(nextAction({ status: "success", result: "data_v1" }), "save: data_v1");

console.log(`action: ${nextAction({ status: "pending", retries: 1 })}`);

JavaScript 在 default 分支抛出带有上下文详细信息的异常是防御漏判的唯一途径。

# TypeScript

TypeScriptconst assert = {
  equal: (a: unknown, b: unknown) => { if (a !== b) throw new Error(`${a} !== ${b}`); },
  deepEqual: (a: unknown, b: unknown) => { if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(`${JSON.stringify(a)} !== ${JSON.stringify(b)}`); },
  throws: (fn: () => void, errType?: unknown) => {
    try { fn(); throw new Error("expected throw"); } catch (e: unknown) {
      if (e instanceof Error && e.message === "expected throw") throw e;
      if (typeof errType === "function" && !(e instanceof errType)) throw e;
    }
  }
};

interface Pending {
  readonly status: "pending";
  readonly retries: number;
}
interface Success {
  readonly status: "success";
  readonly result: string;
}
interface Failed {
  readonly status: "failed";
  readonly code: number;
  readonly message: string;
}

type TaskState = Pending | Success | Failed;

function assertNever(x: never): never {
  throw new Error(`Unexpected object: ${x}`);
}

function nextAction(state: TaskState): string {
  switch (state.status) {
    case "pending":
      return state.retries < 3 ? "retry" : "abandon";
    case "success":
      return `save: ${state.result}`;
    case "failed":
      return `notify: ${state.message}`;
    default:
      // 穷尽性检查:若上面漏写分支,state 在此处不是 never,编译直接报错!
      return assertNever(state);
  }
}

assert.equal(nextAction({ status: "pending", retries: 1 }), "retry");
assert.equal(nextAction({ status: "success", result: "data_v1" }), "save: data_v1");

console.log(`action: ${nextAction({ status: "pending", retries: 1 })}`);

TypeScript 的 never 类型守卫是工业级前端代码保证状态覆盖率的关键技巧。

# Kotlin

Kotlinsealed interface TaskState {
    data class Pending(val retries: Int) : TaskState
    data class Success(val result: String) : TaskState
    data class Failed(val code: Int, val message: String) : TaskState
}

fun nextAction(state: TaskState): String {
    // 作为表达式的 when:省略 else 强制穷尽检查
    return when (state) {
        is TaskState.Pending -> if (state.retries < 3) "retry" else "abandon"
        is TaskState.Success -> "save: ${state.result}"
        is TaskState.Failed -> "notify: ${state.message}"
    }
}

fun main() {
    check(nextAction(TaskState.Pending(1)) == "retry")
    check(nextAction(TaskState.Pending(3)) == "abandon")
    check(nextAction(TaskState.Success("data_v1")) == "save: data_v1")

    println("action: ${nextAction(TaskState.Pending(1))}")
}

Kotlin 的 when 作为表达式使用时,若未完全覆盖密封类型且未提供 else,编译器直接报错。

# Python

Pythonfrom dataclasses import dataclass
from typing import Union

@dataclass(frozen=True)
class Pending:
    retries: int

@dataclass(frozen=True)
class Success:
    result: str

@dataclass(frozen=True)
class Failed:
    code: int
    message: str

TaskState = Union[Pending, Success, Failed]

def next_action(state: TaskState) -> str:
    match state:
        case Pending(r) if r < 3:
            return "retry"
        case Pending():
            return "abandon"
        case Success(result=res):
            return f"save: {res}"
        case Failed(message=msg):
            return f"notify: {msg}"
        case _:
            raise ValueError("Unhandled state")

if __name__ == "__main__":
    assert next_action(Pending(1)) == "retry"
    assert next_action(Pending(3)) == "abandon"
    assert next_action(Success("data_v1")) == "save: data_v1"

    print(f"action: {next_action(Pending(1))}")

Python 3.10 的模式匹配语法支持在模式后直接跟 if 守卫条件。

# C++

C++#include <iostream>
#include <variant>
#include <string>
#include <cassert>

struct Pending { int retries; };
struct Success { std::string result; };
struct Failed { int code; std::string message; };

using TaskState = std::variant<Pending, Success, Failed>;

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

std::string next_action(const TaskState& state) {
    return std::visit(overloaded {
        [](const Pending& p) { return p.retries < 3 ? "retry" : "abandon"; },
        [](const Success& s) { return "save: " + s.result; },
        [](const Failed& f) { return "notify: " + f.message; }
    }, state);
}

int main() {
    assert(next_action(Pending{1}) == "retry");
    assert(next_action(Pending{3}) == "abandon");
    assert(next_action(Success{"data_v1"}) == "save: data_v1");

    std::cout << "action: " << next_action(Pending{1}) << "\n";
}

C++ 中若 overloaded 漏掉任何一个 variant 内部类型的重载,模板实例化在编译期立即崩溃,提供可靠的穷尽性。

# 迁移误区与适用限制

# 运行与验证

语言版本基线见示例基线。 所有程序运行后控制台输出均为:

纯文本action: retry

守卫分支跳转(重试与放弃)、成功保存与失败提取测试全部通过。

# 相关阅读