AI Library · 知识库

# 多分支处理:按命令名称执行操作

学习目标:用同一组命令比较 switch、match 与 when,并让未知命令成为显式失败。

前置知识与范围:字符串、函数调用与条件判断;只做同步命令分派,不启动进程,不涉及权限或服务状态机。

# 问题与边界

输入是非空或空的字符串,逐字、区分大小写匹配;不 trim,不自动转小写。start 返回 started,stop 返回 stopped,status 与别名 st 返回 idle。这些返回值代表本例的操作结果,不声称已经启动真实服务。其他输入(含空串、START、 start)失败,调用方显示 unknown command,不得执行某个默认操作。

每次调用最多选择一个分支。示例断言覆盖全部合法命令和三个非法命令,再展示一次成功与一次失败。

# 关键差异

写法 本例的选择 要注意的边界
Rust match 表达式返回 Result _ 覆盖未知字符串
Go switch case 中直接返回 默认不贯穿,无须 break
Java switch 表达式 箭头分支返回字符串 箭头不贯穿,未知值抛异常
JS / TS switch case 中直接返回 传统 case 会贯穿;return 在此阻止贯穿
Kotlin when 表达式返回字符串 开放字符串输入需要 else
Python match 字面量模式和 OR 模式 裸名称通常是捕获,不是常量比较
C++ 条件链 字符串比较返回 optional 原生 switch 不接受字符串,不伪造整数哈希分派

# 各语言实现

下列程序各自完整,使用标准库,分别保存到独立目录。不要把不同语言或不同文章的同名文件混放。

# Rust

Rustfn dispatch(command: &str) -> Result<&'static str, &'static str> {
    match command {
        "start" => Ok("started"),
        "stop" => Ok("stopped"),
        "status" | "st" => Ok("idle"),
        _ => Err("unknown command"),
    }
}
fn main() {
    for (command, expected) in [("start", "started"), ("stop", "stopped"),
                                ("status", "idle"), ("st", "idle")] {
        assert_eq!(dispatch(command), Ok(expected));
    }
    for command in ["", "START", " start"] {
        assert!(dispatch(command).is_err());
    }
    for command in ["start", "unknown"] {
        match dispatch(command) {
            Ok(message) => println!("{message}"),
            Err(message) => println!("{message}"),
        }
    }
}

match 同时完成分支与取值;返回的是静态字符串,无须为短消息分配 String。Result 让调用方区分成功和失败,尽管本例最终都打印消息。参见 Rust match。

# Go

Gopackage main

import (
    "errors"
    "fmt"
)

func dispatch(command string) (string, error) {
    switch command {
    case "start":
        return "started", nil
    case "stop":
        return "stopped", nil
    case "status", "st":
        return "idle", nil
    default:
        return "", errors.New("unknown command")
    }
}
func main() {
    for command, expected := range map[string]string{
        "start": "started", "stop": "stopped", "status": "idle", "st": "idle",
    } {
        value, err := dispatch(command)
        if err != nil || value != expected { panic("wrong dispatch") }
    }
    for _, command := range []string{"", "START", " start"} {
        if _, err := dispatch(command); err == nil { panic("accepted invalid command") }
    }
    for _, command := range []string{"start", "unknown"} {
        value, err := dispatch(command)
        if err != nil { fmt.Println(err); continue }
        fmt.Println(value)
    }
}

Go 的逗号分隔 case 是同一分支的多个值;只有显式 fallthrough 才会继续进入下一 case,本例不需要。失败分支先检查 error,不消费空的占位返回值。参见 Go switch 规范。

# Java

Javapublic class Main {
    static String dispatch(String command) {
        return switch (command) {
            case "start" -> "started";
            case "stop" -> "stopped";
            case "status", "st" -> "idle";
            default -> throw new IllegalArgumentException("unknown command");
        };
    }
    public static void main(String[] args) {
        String[][] cases = {{"start", "started"}, {"stop", "stopped"},
                            {"status", "idle"}, {"st", "idle"}};
        for (String[] item : cases) {
            if (!dispatch(item[0]).equals(item[1])) throw new AssertionError();
        }
        for (String command : new String[]{"", "START", " start"}) {
            try { dispatch(command); throw new AssertionError(); }
            catch (IllegalArgumentException expected) { /* 拒绝非法输入 */ }
        }
        for (String command : new String[]{"start", "unknown"}) {
            try { System.out.println(dispatch(command)); }
            catch (IllegalArgumentException e) { System.out.println(e.getMessage()); }
        }
    }
}

箭头分支不同于传统冒号 case,不会意外穿透;switch 表达式必须产生值或抛异常。输入契约不含 null。参见 Java 21 switch 表达式。

# JavaScript

JavaScriptfunction dispatch(command) {
    switch (command) {
        case "start": return "started";
        case "stop": return "stopped";
        case "status":
        case "st": return "idle";
        default: throw new RangeError("unknown command");
    }
}
for (const [command, expected] of [
    ["start", "started"], ["stop", "stopped"], ["status", "idle"], ["st", "idle"]
]) {
    if (dispatch(command) !== expected) throw new Error("wrong dispatch");
}
for (const command of ["", "START", " start"]) {
    try { dispatch(command); throw new Error("accepted invalid command"); }
    catch (error) { if (!(error instanceof RangeError)) throw error; }
}
for (const command of ["start", "unknown"]) {
    try { console.log(dispatch(command)); }
    catch (error) {
        if (!(error instanceof RangeError)) throw error;
        console.log(error.message);
    }
}

两个相邻 case 有意共享一条操作;其他 case 的 return 退出函数,不只退出 switch。这里不需要再写 break。参见 ECMAScript switch。

# TypeScript

TypeScriptfunction dispatch(command: string): string {
    switch (command) {
        case "start": return "started";
        case "stop": return "stopped";
        case "status":
        case "st": return "idle";
        default: throw new RangeError("unknown command");
    }
}
for (const [command, expected] of [
    ["start", "started"], ["stop", "stopped"], ["status", "idle"], ["st", "idle"]
]) {
    if (dispatch(command) !== expected) throw new Error("wrong dispatch");
}
for (const command of ["", "START", " start"]) {
    try { dispatch(command); throw new Error("accepted invalid command"); }
    catch (error) { if (!(error instanceof RangeError)) throw error; }
}
for (const command of ["start", "unknown"]) {
    try { console.log(dispatch(command)); }
    catch (error) {
        if (!(error instanceof RangeError)) throw error;
        console.log(error.message);
    }
}

仍使用 string 而非只包含合法命令的联合类型,因为这个边界必须接受并拒绝未知命令。catch 中的 instanceof 将未知错误收窄后才读消息。参见 TypeScript narrowing。

# Kotlin

Kotlinfun dispatch(command: String): String = when (command) {
    "start" -> "started"
    "stop" -> "stopped"
    "status", "st" -> "idle"
    else -> throw IllegalArgumentException("unknown command")
}
fun main() {
    for ((command, expected) in listOf("start" to "started", "stop" to "stopped",
                                      "status" to "idle", "st" to "idle")) {
        check(dispatch(command) == expected)
    }
    for (command in listOf("", "START", " start")) {
        try { dispatch(command); error("accepted invalid command") }
        catch (expected: IllegalArgumentException) { /* 拒绝非法输入 */ }
    }
    for (command in listOf("start", "unknown")) {
        try { println(dispatch(command)) }
        catch (e: IllegalArgumentException) { println(e.message) }
    }
}

when 用于取值时必须覆盖所有情况;开放字符串不是封闭枚举,所以保留 else。参见 Kotlin when。

# Python

不要使用会关闭断言的 -O。

Pythondef dispatch(command: str) -> str:
    match command:
        case "start":
            return "started"
        case "stop":
            return "stopped"
        case "status" | "st":
            return "idle"
        case _:
            raise ValueError("unknown command")

for command, expected in [("start", "started"), ("stop", "stopped"),
                          ("status", "idle"), ("st", "idle")]:
    assert dispatch(command) == expected
for command in ["", "START", " start"]:
    try:
        dispatch(command)
    except ValueError:
        pass  # 这个边界用例预期被拒绝
    else:
        raise AssertionError("accepted invalid command")
for command in ["start", "unknown"]:
    try:
        print(dispatch(command))
    except ValueError as error:
        print(error)

这是字面量模式匹配,不是 C 风格 switch。把 case "start" 改成 case start 会捕获值而不是比较同名变量;| 表示任选一种模式。参见 Python match 教程。

# C++

C++#include <cassert>
#include <iostream>
#include <optional>
#include <string_view>
using namespace std::literals;

std::optional<std::string_view> dispatch(std::string_view command) {
    if (command == "start") return "started"sv;
    if (command == "stop") return "stopped"sv;
    if (command == "status" || command == "st") return "idle"sv;
    return std::nullopt;
}
int main() {
    assert(dispatch("start") == "started"sv);
    assert(dispatch("stop") == "stopped"sv);
    assert(dispatch("status") == "idle"sv);
    assert(dispatch("st") == "idle"sv);
    for (auto command : {"", "START", " start"}) assert(!dispatch(command));
    for (auto command : {"start", "unknown"}) {
        if (auto value = dispatch(command)) std::cout << *value << '\n';
        else std::cout << "unknown command\n";
    }
}

字符串条件链更直接,避免为了展示 switch 再引入解析枚举。返回的 string_view 指向静态字面量,不指向局部字符串。参见 C++ switch 工作草案;在线草案会变化,不用其新功能作为 C++17 兼容证明。

# 迁移误区与适用限制

# 运行与验证

语言版本统一见示例基线,实际执行状态见验证记录。本篇给出的是预期行为,不宣称已经运行通过;各语言按上方命令独立执行。Java 使用显式检查而非默认关闭的 assert;C++ 不要定义 NDEBUG,Python 不使用优化模式,以保留示例断言。

驱动覆盖四个合法入口、空串、大小写错误、前导空格和普通未知命令。合法别名产生相同值,未知命令由最外层调用方报告;未覆盖真实服务的 I/O 与状态转换。

预期标准输出:按顺序输出 started、unknown command,每项独占一行,末尾有换行。 断言失败或未预期异常应使运行失败,而不是继续输出成功提示。

# 相关阅读