本节摘要:控制流规范的目标是让读者顺着主干走不迷路。本节讲条件语句的纪律(嵌套三层封顶、正向条件优先、常见条件前置、三元运算符边界)、循环的选择与禁忌、卫语句作为压平的核心手法,以及当分支复杂到 if-else 管不住时,状态机与策略模式如何收编混乱。
阅读完本节,你应当能够:
有一种代码被工程师们称为"箭头反模式"——每层条件向右缩进一格,整体形状像一支向右飞的箭头:
if a: if b: if c: if d: do_the_real_work() else: handle_not_d() else: handle_not_c() else: handle_not_b() else: handle_not_a()
读这种代码,你要在脑中维护一个条件栈:读到最深处时还记得进来时 a、b、c 各是什么吗?else 分支更糟——handle_not_b 对应的是外层第二个条件取反,读者要在脑中回溯缩进层级才能配对。维护这种代码的经典事故:有人要在最深处改一行逻辑,没注意到外层某个 else 的存在,改出了只有特定条件组合下才触发的缺陷。
压平的钥匙在第 2.1 节已经亮过相:卫语句。把"不满足就退出"的处理全部前移到函数开头,主干自然落到最左侧。本节把这套手法系统化,并处理卫语句管不住的更复杂场景。
嵌套三层封顶。超过三层是重构信号,手法按优先级:卫语句提前返回(挡异常条件)、提取函数(把内层逻辑变成调用)、多态分发(用类型系统替代类型判断链)。
正向条件优先。if (isValid) 优于 if (!isInvalid)——大脑处理否定要多绕一步,双重否定(if (!isNotReady))更是灾难。布尔命名配正向条件(第 2.2 节:用 isValid 不用 isNotValid),两个规范在这里会师。
常见条件前置。多分支判断时,把最常发生或最重要的条件放在最前。这不只是可读性——读者最关心的路径最先读到,也是性能:热路径少做几次无效判断。
简化布尔表达式。if (a == true && b == false) 直接写 if (a && !b);复杂的长表达式提取为具名布尔变量 isEligibleForDiscount = ...,条件读起来像句子。三元运算符只用于简单的条件赋值(一行能看清真假两分支),嵌套三元或带副作用的三元一律改为完整 if-else。短路求值(逻辑与或的非短路变体)可以写出简洁的保护性判断——先判空再取属性的惯用写法,但别拿它编排复杂业务逻辑,会走向另一个"聪明过头"的极端。
按需选型:已知次数用 for 计数循环;未知次数但有明确退出条件用 while;至少执行一次才判断用 do-while;遍历集合用逐元素循环(现代语言的默认选择,免去下标越界与心智负担)。
三条禁忌:禁忌一,无明确退出条件的循环——确保每条路径都能走到出口,否则就是一个挂着的服务;禁忌二,循环体内的重计算——与循环变量无关的计算提出循环外(这在第 4.3 节的"先测量再优化"框架下是少数无需测量就该做的优化,因为它同时改善可读性);禁忌三,滥用 break 与 continue——偶尔一个提前跳出让逻辑更清晰,但一坨跳转语句会把循环变成迷宫,出现第三个跳转时就该考虑提取函数或重构。
# 循环内重计算 每轮白算一次 for item in items: rate = lookup_base_rate() # 与循环无关 charge(item, rate) # 提出循环外 rate = lookup_base_rate() for item in items: charge(item, rate)
分支复杂度有两个升级信号。信号一:一个变量在代码各处被反复与一组值比较(订单状态在十几个函数里各有十种分支)——这是状态机场景:把每个状态的合法转换与行为集中定义,状态间的迁移规则一目了然,非法转换在定义层面被拒绝。信号二:同一个判断点要根据条件在多种算法间切换(满减、折扣、赠品三种计价逻辑)——这是策略模式场景:每种算法独立成类实现同一接口,新增算法加类即可,不用再去长串条件里插分支。

分支还有一种被低估的替代品:查表。一批"条件对应动作"的分支(每种消息类型对应一个处理函数、每个地区对应一种税率),可以表示为映射表。新增一项只需加一行表项,而非复制一段 if-else;表还可以数据化(放配置、做校验)。经验法则:分支在比较"值"时优先查表,在比较"结构"时才需要策略模式。
递归解决树形结构遍历等问题时代码优雅,但它对读者的工作记忆要求高(脑中维护调用栈),深度失控还会真的爆栈。规范态度:树形与分形结构用递归天然合适;线性问题用循环更直白;用递归时必须有明确的终止条件,且把递归体提取为独立函数。不要为了炫技把循环改写成递归。
⚠️ 常见坑:把"压平嵌套"做成五连跳——连续五个
if ...: return之后又接五个if ...: continue,主逻辑确实贴左了,但读者要在脑中把这十个条件取反合并才能知道"到底什么情况下执行"。卫语句的数量与语义粒度要克制:拦截的应是异常与边界条件,正常业务的多样性交给主干里的顺序逻辑。
💡 关键直觉:控制流的读者模型是"顺着主干走,随时能跳过岔路"。任何让主干右移、让读者维护条件栈的写法都在对抗这个模型;卫语句、表驱动、策略模式都是把"条件栈"变成"条件清单",清单是可以逐条独立阅读的。
| 场景 | 首选手法 | 禁忌 |
|---|---|---|
| 函数内异常条件 | 卫语句提前返回 | 包成深层嵌套 |
| 简单条件赋值 | 三元运算符 | 嵌套三元 |
| 集合遍历 | 逐元素循环 | 手写下标心智负担 |
| 条件对应动作成批出现 | 表驱动查表 | 复制粘贴 if 链 |
| 一点多种算法 | 策略模式 | 长串类型判断 |
| 多状态多处分支 | 状态机 | 状态判断散落各处 |
下一节讨论比嵌套更隐蔽的复杂度来源:我们自己发明出来的——过度设计与过早优化。