# 多分支处理:按命令名称执行操作
学习目标:用同一组命令比较 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 兼容证明。
# 迁移误区与适用限制
- 将 Go 的“不贯穿”经验照搬到 JavaScript 冒号 case,删除
return且继续执行操作,会使一次命令触发多条逻辑。箭头 switch、match、when 的规则不能反推所有 switch 都一样。 - 自动 trim 或转小写会把本应拒绝的
start、START接受为合法命令,是协议变更,不是无害清理。 - 未知命令不应落到
start之类有副作用的默认操作。若真实操作会失败,分派成功与操作成功必须分别建模;本例只示范分派。
# 运行与验证
语言版本统一见示例基线,实际执行状态见验证记录。本篇给出的是预期行为,不宣称已经运行通过;各语言按上方命令独立执行。Java 使用显式检查而非默认关闭的 assert;C++ 不要定义 NDEBUG,Python 不使用优化模式,以保留示例断言。
驱动覆盖四个合法入口、空串、大小写错误、前导空格和普通未知命令。合法别名产生相同值,未知命令由最外层调用方报告;未覆盖真实服务的 I/O 与状态转换。
预期标准输出:按顺序输出 started、unknown command,每项独占一行,末尾有换行。 断言失败或未预期异常应使运行失败,而不是继续输出成功提示。
# 相关阅读
- if / else:判断库存:比较连续范围而非离散命令。
- 函数与参数:把参数与失败契约写清楚。