Skip to content

第 4 章:流转 — 当代码开始分岔

一个简单的任务,和它背后不简单的问题

根据订单状态决定下一步动作:

  • 未支付 → 提醒用户
  • 已支付 → 检查是否超过 30 天,超过则标记为"已完成",未超过则等待发货
  • 已发货 → 确认收货倒计时
  • 已取消 → 存档
  • 未知状态 → 报警

任何程序员都能写出这段逻辑。但你有没有想过——

为什么有的语言写完就完了,有的语言编译器会追问你:"你确定你处理了所有可能的状态吗?"

这不是语法的差异。这是语言对 "逻辑完整性" 的态度。


4.1 语法基础:8 种语言如何让代码分岔

Python

python
# if/elif/else:Python 最日常的分支
status = order.status

if status == "unpaid":
    remind_user(order)
elif status == "paid":
    if order.days_since_payment() > 30:
        complete_order(order)
    else:
        await_shipment(order)
elif status == "shipped":
    start_countdown(order)
elif status == "cancelled":
    archive_order(order)
else:
    alert(f"unknown status: {status}")

# Python 3.10+ 的模式匹配(structural pattern matching)
match status:
    case "unpaid":
        remind_user(order)
    case "paid" if order.days_since_payment() > 30:
        complete_order(order)
    case "paid":
        await_shipment(order)
    case "shipped":
        start_countdown(order)
    case "cancelled":
        archive_order(order)
    case _:
        alert(f"unknown status: {status}")

Python 的 match/case(3.10+)是相对较新的特性,比 if/elif 更擅长处理嵌套结构和守卫条件。但它不强制穷尽性——漏掉一个分支,只有运行时才会发现。

JavaScript / TypeScript

typescript
// 传统 if/else
if (status === "unpaid") {
    remindUser(order);
} else if (status === "paid") {
    if (order.daysSincePayment() > 30) {
        completeOrder(order);
    } else {
        awaitShipment(order);
    }
} else if (status === "shipped") {
    startCountdown(order);
} else if (status === "cancelled") {
    archiveOrder(order);
} else {
    alert(`unknown status: ${status}`);
}

// switch(带 break,容易忘)
switch (status) {
    case "unpaid":
        remindUser(order);
        break;
    case "paid":
        if (order.daysSincePayment() > 30) {
            completeOrder(order);
        } else {
            awaitShipment(order);
        }
        break;
    case "shipped":
        startCountdown(order);
        break;
    case "cancelled":
        archiveOrder(order);
        break;
    default:
        alert(`unknown status: ${status}`);
}

// TypeScript 的 discriminated union + switch —— 编译器能检查穷尽性
type OrderStatus = "unpaid" | "paid" | "shipped" | "cancelled";

function handleOrder(order: { status: OrderStatus }): void {
    switch (order.status) {
        case "unpaid": return remindUser(order);
        case "paid": return handlePaid(order);
        case "shipped": return startCountdown(order);
        case "cancelled": return archiveOrder(order);
        // 如果漏掉一个 case,TypeScript 编译器会报错
        // 因为返回类型是 void,而非 never
    }
}

Java

java
// 传统 switch(Java 17 之前)
switch (status) {
    case "unpaid":
        remindUser(order);
        break;
    case "paid":
        handlePaid(order);
        break;
    case "shipped":
        startCountdown(order);
        break;
    case "cancelled":
        archiveOrder(order);
        break;
    default:
        alert("unknown status: " + status);
}

// Java 17+:增强型 switch + 模式匹配
String action = switch (status) {
    case "unpaid" -> {
        remindUser(order);
        yield "reminded";
    }
    case "paid" -> {
        handlePaid(order);
        yield "handled";
    }
    case "shipped" -> {
        startCountdown(order);
        yield "counting";
    }
    case "cancelled" -> {
        archiveOrder(order);
        yield "archived";
    }
    default -> {
        alert("unknown status: " + status);
        yield "unknown";
    }
};

// Java 的 switch 表达式强制穷尽性——如果 enum switch 漏了 case,编译器报错

C++

cpp
// if/else
if (status == "unpaid") {
    remind_user(order);
} else if (status == "paid") {
    handle_paid(order);
} else if (status == "shipped") {
    start_countdown(order);
} else if (status == "cancelled") {
    archive_order(order);
} else {
    alert("unknown status: " + status);
}

// switch(只适用于整数和枚举类型!注意 C++ switch 不能直接用于 string)
switch (status_code) {  // 需要先转成枚举或整数
    case Status::Unpaid:
        remind_user(order);
        break;          // 别忘了 break!否则 fall through
    case Status::Paid:
        handle_paid(order);
        break;
    case Status::Shipped:
        start_countdown(order);
        break;
    case Status::Cancelled:
        archive_order(order);
        break;
    default:
        alert("unknown status");
}

Rust

rust
// match:Rust 最标志性的控制流结构
match order.status {
    Status::Unpaid => remind_user(&order),
    Status::Paid => {
        if order.days_since_payment() > 30 {
            complete_order(&order);
        } else {
            await_shipment(&order);
        }
    }
    Status::Shipped => start_countdown(&order),
    Status::Cancelled => archive_order(&order),
    // 如果 Status 是 enum,漏写一个分支 → 编译错误
    // 如果加了 #[non_exhaustive],需要通配分支
}

// if let:只匹配一个模式
if let Status::Paid = order.status {
    println!("order is paid");
}

// matches! 宏:返回布尔值
let is_done = matches!(order.status, Status::Shipped | Status::Cancelled);

Go

go
// if/else if:Go 最日常的分支
if status == "unpaid" {
    remindUser(order)
} else if status == "paid" {
    if order.DaysSincePayment() > 30 {
        completeOrder(order)
    } else {
        awaitShipment(order)
    }
} else if status == "shipped" {
    startCountdown(order)
} else if status == "cancelled" {
    archiveOrder(order)
} else {
    alert("unknown status: " + status)
}

// switch:Go 的 switch 不 fall through(除非显式写 fallthrough)
switch status {
case "unpaid":
    remindUser(order)
case "paid":
    if order.DaysSincePayment() > 30 {
        completeOrder(order)
    } else {
        awaitShipment(order)
    }
case "shipped":
    startCountdown(order)
case "cancelled":
    archiveOrder(order)
default:
    alert("unknown status: " + status)
}

// switch 不带表达式 = 更清晰的 if/else 替代
switch {
case amount < 0:
    return fmt.Errorf("invalid amount")
case amount == 0:
    return fmt.Errorf("zero amount")
default:
    // proceed
}

Swift

swift
// switch:强穷尽性检查
switch order.status {
case .unpaid:
    remindUser(order)
case .paid:
    if order.daysSincePayment() > 30 {
        completeOrder(order)
    } else {
        awaitShipment(order)
    }
case .shipped:
    startCountdown(order)
case .cancelled:
    archiveOrder(order)
// 如果漏了 enum case,编译器报错
}

// guard:提前退出的 Swift 标志性语法
func process(_ order: Order) throws {
    guard order.amount >= 0 else {
        throw ValidationError.invalidAmount
    }
    // 正常流程——amount 已经确定为非负
}

Kotlin

kotlin
// when:Kotlin 的增强型 switch
when (status) {
    "unpaid" -> remindUser(order)
    "paid" -> {
        if (order.daysSincePayment() > 30) completeOrder(order)
        else awaitShipment(order)
    }
    "shipped" -> startCountdown(order)
    "cancelled" -> archiveOrder(order)
    else -> alert("unknown status: $status")
}

// when 作为表达式
val action = when (status) {
    "unpaid" -> "remind"
    "paid" -> "handle"
    "shipped" -> "countdown"
    "cancelled" -> "archive"
    else -> "unknown"
}

// when 用于 enum 时,如果不写 else 且漏了 case,编译器报错
enum class Status { UNPAID, PAID, SHIPPED, CANCELLED }
when (status) {
    Status.UNPAID -> remindUser(order)
    Status.PAID -> handlePaid(order)
    Status.SHIPPED -> startCountdown(order)
    Status.CANCELLED -> archiveOrder(order)
    // 不需要 else——编译器检查穷尽性
}

语法速查:分支与循环

能力PythonTypeScriptJavaC++RustGoSwiftKotlin
if/else
switch/matchmatch/case (3.10+)switchswitch (+ 增强版 17+)switch (仅整数/enum)matchswitch (无 fallthrough)switchwhen
穷尽性检查否(match 也不强制)TypeScript discriminated union 可enum switch 可编译器可警告强制(编译错误)强穷尽性enum when 强制
表达式 vs 语句语句 + match 表达式语句 + 三元语句 + switch 表达式 (17+)语句 + 三元一切皆表达式语句语句 + switch 表达式语句 + when 表达式
守卫/条件if 守卫 (match)if 在 case 中when 子句if 守卫switch 无表达式whereif 在 when 中
提前退出returnreturnreturnreturnreturn / ?returnguardreturn / ?:

4.2 深度对照一:表达式 vs 语句 — 这件事比你想象的深

在大多数语言里,if 是一个语句——它执行动作,但不返回值:

java
// Java(传统)
String action;
if (status.equals("paid")) {
    action = "complete";
} else {
    action = "pending";
}

在另一些语言里,if 是一个表达式——它产生值:

rust
// Rust
let action = if status == Status::Paid {
    "complete"
} else {
    "pending"
};

这两种写法看起来差异不大——直到你开始嵌套。

语句世界的痛

java
// Java 语句风格:声明和赋值分离
String level;
if (amount > 1000) {
    if (paid) {
        level = "vip-premium";
    } else {
        level = "high-value";
    }
} else {
    if (paid) {
        level = "standard";
    } else {
        level = "to-remind";
    }
}

你需要在脑子里同时追踪 "每个分支最后一定会给 level 赋值吗"。如果漏了一个分支,level 可能是未初始化状态——这取决于编译器的确定赋值分析(definite assignment analysis)。

表达式世界的优雅

rust
// Rust 表达式风格:声明即赋值
let level = if amount > 1000 {
    if paid { "vip-premium" } else { "high-value" }
} else {
    if paid { "standard" } else { "to-remind" }
};

变量声明和值绑定在一起。编译器强制每个分支都产生值——漏一个分支,编译不过。表达式风格让 "变量初始化完整" 这件事从 "靠人工检查" 变成 "靠编译器检查"。

谁选择了什么

语言if 地位switch/match 地位
Rust表达式表达式
Kotlin语句when 可以是表达式
Swift语句switch 可以是表达式
Java (17+)语句switch 可以是表达式
Python (3.10+)语句match 可以是表达式
Go语句语句
C++语句语句
JavaScript语句(三元除外)语句

为什么不是所有人都切成表达式? 因为表达式风格也有代价:它倾向于产生更多嵌套,而深层嵌套的可读性不好。你需要一个平衡——像 Rust 的 ? 操作符、Swift 的 guard、Kotlin 的 ?: 这些提前返回/提前退出的工具,让表达式风格可以不依赖深层嵌套。


4.3 深度对照二:穷尽性检查 — 编译器追问你的那些问题

把订单状态定义成枚举,然后在分支里漏掉一个 case。

Rust:不通过就别想编译

rust
enum Status { Unpaid, Paid, Shipped, Cancelled }

fn handle(status: Status) {
    match status {
        Status::Unpaid => remind(),
        Status::Paid => process(),
        Status::Shipped => track(),
        // 漏了 Cancelled → 编译错误:
        // error[E0004]: non-exhaustive patterns: `Status::Cancelled` not covered
    }
}

Swift:同样强制

swift
enum Status { case unpaid, paid, shipped, cancelled }

func handle(_ status: Status) {
    switch status {
    case .unpaid: remind()
    case .paid: process()
    case .shipped: track()
    // 漏了 .cancelled → 编译错误:
    // Switch must be exhaustive
    }
}

Kotlin:enum + when 强制

kotlin
enum class Status { UNPAID, PAID, SHIPPED, CANCELLED }

fun handle(status: Status) {
    when (status) {
        Status.UNPAID -> remind()
        Status.PAID -> process()
        Status.SHIPPED -> track()
        // 漏了 CANCELLED → 编译错误:
        // 'when' expression must be exhaustive, add necessary 'CANCELLED' branch or 'else' branch instead
    }
}

Java:部分检查

java
// Java:enum switch 如果漏了 case,IDE 会警告,但不是编译器错误(除非用 switch 表达式)
String action = switch (status) {  // switch 表达式强制穷尽
    case UNPAID -> "remind";
    case PAID -> "process";
    case SHIPPED -> "track";
    // 漏了 CANCELLED → 编译错误
};

Python / JavaScript / Go:不检查

python
match status:
    case "unpaid": remind()
    case "paid": process()
    case "shipped": track()
    # 漏了 cancelled —— 完全没问题,匹配不到就什么都不做

穷尽性检查的真正价值

不是你写的时候会被提醒。而是:

三个月后,有人给 Status 加了一个 Refunded 状态。穷尽性检查会让所有需要处理新状态的地方全体报编译错误。没有被检查的语言里,这些地方会安静地带着"未知状态走 default"上线。

穷尽性匹配不是在帮你写今天的代码。它是在帮你把未来的变更风险从 "靠勇气和测试覆盖" 变成 "靠编译器强制追踪"。


4.4 深度对照三:循环的那些隐藏分岔

for 循环的基础形态(8 门语言都会)

python
for order in orders:
    process(order)

但这只是循环世界的第一层。往下看。

提前退出:breakcontinue 的普遍存在

所有 8 门语言都有 breakcontinue。但有些语言可以做得更多:

java
// Java:标签 break 可以跳出多层循环
outer:
for (var batch : batches) {
    for (var order : batch) {
        if (order.isError()) {
            break outer;  // 直接跳出最外层循环
        }
    }
}
rust
// Rust:loop 标签 + break 带值
let result = 'search: loop {
    for batch in &batches {
        for order in batch {
            if order.is_target() {
                break 'search Some(order.id.clone());
            }
        }
    }
    break None;  // 没找到
};

Rust 的 loop 是特殊的——它不仅支持标签 break,还能从循环中返回值。这是因为 loop 是一个表达式。

Go 的 range 是朴素的——你得不到索引以外的信息

go
for _, order := range orders {
    // 你现在在处理第几个?如果你没在前面声明 i,你就不知道
    // 后面还剩几个?不知道
    // 当前是最后一个吗?不知道
}
python
# Python 的 enumerate + 推导式可以知道更多
for i, order in enumerate(orders):
    remaining = len(orders) - i - 1
    is_last = (i == len(orders) - 1)
rust
// Rust 的迭代器给了你全部控制权
for (i, order) in orders.iter().enumerate() {
    let remaining = orders.len() - i - 1;
    let is_last = i == orders.len() - 1;
}

这不是说 Python 和 Rust 的循环 "更好"。而是说 Go 刻意保持了 range 的简单性——你可以自己追踪索引,但语言不帮你做。这符合 Go 的总体哲学:给你一套最朴素的工具,你就不会写出太花哨的代码。


4.5 边界探索:跨语言控制流陷阱

陷阱一:C++ → 任何现代语言:switch 的 fall through

cpp
// C++:switch 默认 fall through
switch (status) {
    case Status::Paid:
        process_payment();
        // 忘了 break!会继续执行下面的 Shipped 逻辑
    case Status::Shipped:
        start_shipping();  // 即使 status 是 Paid 也会执行这里
        break;
}

这可能是 C++(和 C/Java/JavaScript)最臭名昭著的设计缺陷之一。Go、Swift、Kotlin 都修复了这个问题——默认不 fall through。Rust 的 match 完全不 fall through。

陷阱二:JavaScript → Rust:没有 else if,只有 else { if }

rust
// 从 JavaScript 来,很多人会写:
if status == Status::Paid {
    handle_paid();
} else if status == Status::Shipped {  // 编译错误!Rust 没有 else if 关键字
    handle_shipped();
}

Rust 没有 else if 关键字——但你可以组合 elseif

rust
if status == Status::Paid {
    handle_paid();
} else if status == Status::Shipped {  // 这其实是 else { if ... } 的缩写,恰好合法
    handle_shipped();
}

实际上编译器把它解成 else { if ... },效果一样但在极端缩进场景会变丑。

陷阱三:Python → Go:elifelse if 的肌肉记忆

python
# Python
if x > 0:
    pass
elif x == 0:  # Python 是 elif
    pass
go
// Go
if x > 0 {
    // ...
} else if x == 0 {  // Go 是 else if(两个词)
    // ...
}

陷阱四:TypeScript → Go:习惯了 switch 表达式

typescript
// TypeScript 程序员习惯的模式
const result = (() => {
    switch (status) {
        case "paid": return "ok";
        default: return "unknown";
    }
})();

切到 Go 后,你会发现 Go 的 switch 不是表达式——你需要先声明变量再赋值。这是一种微妙的习惯调整:从"一切都能产生值"的世界到"有些东西只是执行"的世界。


4.6 洞察:控制流是你三个月后会不会恨自己的预告

如果你只用一门语言写分支逻辑,你不会发现:

控制流的差异不在于能不能完成逻辑——任何语言都能。差异在于三个月后,当有人(包括你自己)需要在这段代码里加一个新的分支时,语言给不给脚手架。

在没有穷尽性检查的语言里(Python、JavaScript、Go),加一个新状态意味着你需要在全代码库里人肉搜索所有 if/else 链和 switch 块,找到所有需要加新分支的地方。漏一个,就是生产 bug。

在有穷尽性检查的语言里(Rust、Swift、Kotlin、Java 17+),编译器会替你找到所有遗漏。它变成了一个变更追踪系统

这听起来像是一个小功能。但在一个有 200 个状态判断、20 个开发者的系统里,它是维护成本的决定性差异

编译器追问你的问题,不是不信任你。是它已经见过太多和你一样优秀、一样累、一样会忘的程序员,在上线后的凌晨被报警叫醒。


本章小结

  • 穷尽性检查不是语法特征,是维护成本分摊制度。 今天看起来多写了一个 case,明天就少了一个凌晨报警。

  • 表达式 vs 语句不是审美偏好。 它决定 "变量初始化是否完整" 这个问题的答案是从编译器那里来,还是从你的自律那里来。

  • Go 的克制和 Rust 的激进,在控制流上表现得最明显。 Go 的 switch 没有穷尽性检查,for 没有 enumerate。这是刻意的——Go 选择让代码一眼看得懂,代价是让静态检查帮你做的事更少。

  • 模式匹配的进化路径很清楚。 从 C 的 switch(只能匹配整数常量)到 Rust/Swift/Kotlin 的全面模式匹配(解构 + 守卫 + 穷尽性),控制流结构的表达能力提升了一个数量级。


如果你只用一门语言,你不会学到这个:

最容易写出 bug 的代码,不是复杂的代码。而是你写完觉得 "这太简单了不可能出错" 的 if/else 链——因为语言不会帮你检查它。