
1. 从能跑就行到逻辑闭环选择语句练习的真正价值很多人学编程语法时有个习惯看完if-else的写法随手敲两行判断就觉得自己会了。比如写个判断成绩是否及格输入 60 输出及格输入 59 输出不及格跑通了关掉编辑器觉得自己掌握了。但等到真正写业务逻辑——比如根据用户会员等级、订单金额、优惠券类型三个条件叠加计算最终折扣——立刻卡壳写出来的代码嵌套五六层自己过两天都看不懂。这就是选择语句的练习这个主题存在的意义。它表面上是在练if、else if、else、switch这些基础语法实际上练的是把现实世界的复杂条件翻译成计算机能执行的判断链路的能力。这个能力不过关后面学循环、学函数、学面向对象全是空中楼阁。这篇文章适合谁看如果你刚学完选择语句的语法正处在看得懂但写不出的阶段那这篇内容就是为你准备的。如果你已经工作了一两年但写条件逻辑时还是靠堆 if来解决问题那这里面的重构思路和边界处理经验同样对你有用。我会从最基础的练习设计讲起一路讲到多层条件的拆解、switch的适用边界、以及实际项目中条件逻辑的常见坑。需要提前说明的是选择语句本身不复杂语法层面半小时就能学完。真正拉开差距的是练习的质量——你练的是语法默写还是逻辑建模决定了你后面能走多远。下面我按自己带新人和自己踩坑的经验把这件事拆开讲透。2. 练习设计别再用判断奇偶糊弄自己了2.1 为什么大多数选择语句练习是无效的翻一翻常见的编程练习题选择语句部分翻来覆去就那几道判断奇偶数、判断闰年、比较三个数大小、判断成绩等级。这些题有没有用有但作用极其有限。因为它们的特点是条件维度单一、判断链路短、不需要考虑边界。你写判断闰年最多用到%取余加两三个写完就完了脑子里不会留下任何关于如何组织条件的思考。我见过太多人做完这些题之后遇到电商满减规则这种真实场景直接懵掉。满减规则是什么样举个例子普通用户满 100 减 10会员满 100 减 15会员如果同时有优惠券还能叠加但叠加后总折扣不能超过商品原价的 30%且特价商品不参与。你看这里面有条件嵌套、有条件优先级、有边界约束。这才是选择语句真正要练的东西。所以我的建议是语法练习用简单题快速过逻辑练习必须用贴近真实的场景。下面我给几个自己常用的练习设计思路难度递进你可以直接拿去练。2.2 三层递进的练习场景设计第一层单维度多分支。比如根据 BMI 值输出体型分类。BMI 的计算公式是体重kg除以身高m的平方。分类标准低于 18.5 偏瘦18.5 到 24 正常24 到 28 偏胖28 以上肥胖。这个练习的核心不是计算而是让你体会区间判断的边界怎么写。很多人第一次写会写成bmi 18.5 bmi 24然后发现 18.5 这个值两边都不落出现逻辑空洞。正确的写法应该是用else if链让每个分支自然承接上一个分支排除掉的范围。第二层多维度组合。比如电影院票价计算。规则平日成人票 50 元周末成人票 70 元学生票在成人票基础上打七折1.2 米以下儿童免票1.2 到 1.5 米儿童半价。这个练习的难点在于条件的组合方式——你需要先判断是不是儿童再判断是不是学生最后判断平日还是周末。不同的判断顺序会导致代码结构完全不同而好的顺序能让嵌套层数最少。第三层带优先级和约束的规则。比如前面说的电商满减。这一层的核心是让你学会把规则拆成独立的判断单元再按优先级串联。写的时候你会发现如果不用函数把每个规则封装起来主逻辑会膨胀到无法阅读。这就自然引出了后面要讲的条件逻辑的拆分。2.3 练习时该关注什么指标做完一道题别急着下一道。回头看看自己的代码问自己三个问题嵌套层数最深的if嵌套了几层超过三层基本可以判定结构有问题。条件表达式复杂度有没有单个if里塞了四五个和||混用的情况这种表达式极易出错。边界覆盖所有临界值比如 18.5、24、28 这些有没有被正确归类有没有出现两个分支都不管或者都管的区间这三个指标比代码能不能跑重要得多。能跑只是及格线结构清晰、边界严密才是练习的目标。3. if-else 链的写法顺序决定一切3.1 条件顺序为什么会影响正确性先看一段很多人会写出来的代码判断成绩等级score int(input(请输入成绩)) if score 60: print(及格) elif score 70: print(中等) elif score 80: print(良好) elif score 90: print(优秀) else: print(不及格)这段代码能跑但逻辑是错的。输入 95 分第一个条件score 60成立直接输出及格后面的判断根本不会执行。问题出在条件顺序和判断意图不匹配。if-else if链的执行规则是从上到下一旦某个条件成立就跳出整个链。所以条件必须从最严格到最宽松排列或者用互斥的区间来表达。正确的写法有两种。第一种从高到低排if score 90: print(优秀) elif score 80: print(良好) elif score 70: print(中等) elif score 60: print(及格) else: print(不及格)第二种用完整区间表达不依赖顺序if 90 score 100: print(优秀) elif 80 score 90: print(良好) elif 70 score 80: print(中等) elif 60 score 70: print(及格) else: print(不及格)两种写法都对但第一种更简洁第二种更明确。我个人在团队协作中倾向于第二种因为区间写全了后来改代码的人不容易搞错顺序。第一种虽然短但依赖读者知道 else if 的执行规则沟通成本更高。3.2 区间边界那些让人抓狂的临界值区间判断最经典的坑就是边界归属。还是 BMI 那个例子标准是低于 18.5 偏瘦18.5 到 24 正常。那 18.5 到底算偏瘦还是正常24 到底算正常还是偏胖这种问题在需求文档里经常不写清楚得靠你自己定义并保持前后一致。我的习惯是统一采用左闭右开区间也就是[18.5, 24)表示 18.5 包含、24 不包含。这样写的好处是区间之间无缝衔接不会出现空洞也不会重叠。对应代码if bmi 18.5: category 偏瘦 elif bmi 24: category 正常 elif bmi 28: category 偏胖 else: category 肥胖注意这里每个elif都没有写下限因为下限已经被上一个分支的失败条件隐含了。这种写法简洁且不会出错前提是你理解else if 承接的是前面所有条件都不成立的情况。提示涉及金额、年龄、分数这类有明确临界值的判断一定要在代码注释里写清楚边界归属比如# 18.5 归入正常区间。这行注释在半年后救过我不止一次。3.3 嵌套的代价与扁平化技巧嵌套if是新手最容易滥用的结构。比如判断一个用户能不能参加活动if user.is_login: if user.age 18: if user.has_coupon: if order.amount 100: print(可以参加) else: print(订单金额不足) else: print(没有优惠券) else: print(年龄不足) else: print(未登录)四层嵌套读起来像剥洋葱。这种结构的问题不只是难看更致命的是每加一个条件就多一层维护成本指数上升。扁平化的做法是用卫语句guard clause——把不满足条件的情况提前返回或提前处理if not user.is_login: print(未登录) return if user.age 18: print(年龄不足) return if not user.has_coupon: print(没有优惠券) return if order.amount 100: print(订单金额不足) return print(可以参加)两种写法逻辑完全等价但第二种的嵌套层数从四层降到零层每个条件的判断意图一目了然。这个技巧在实际工作中极其常用我几乎在所有需要多层判断的地方都会优先考虑卫语句。4. switch 的适用边界什么时候该用它什么时候不该4.1 switch 和 if-else 的本质区别switch语句在很多语言里都有Python 从 3.10 开始有了matchJava、C、JavaScript 一直都有switch。它的核心特点是基于单个表达式的离散值做分支。也就是说switch判断的是等于某个值而不是满足某个范围。这就决定了switch的适用场景分支数量多、每个分支对应一个明确的离散值、分支之间互斥。典型例子是根据星期几输出对应的活动安排、根据状态码输出错误信息、根据用户选择的菜单项执行对应操作。反过来如果判断涉及范围比如分数段、涉及多个变量的组合条件switch就不合适硬用会写得很别扭。我见过有人用switch写成绩等级把分数除以 10 取整再判断虽然能跑但可读性远不如if-else链。4.2 穿透fall-through是特性还是陷阱switch最容易被误解的机制是穿透——如果某个case后面不写break代码会继续执行下一个case的内容。很多教材把它当成一个需要避免的陷阱来讲但实际上它是个有用的特性只是要用对地方。比如判断某个月份有多少天可以把天数相同的月份合并switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: days 31; break; case 4: case 6: case 9: case 11: days 30; break; case 2: days isLeapYear ? 29 : 28; break; default: days -1; }这里多个case共享同一段逻辑就是穿透的合理用法。但如果你不是故意要穿透忘了写break就会导致 bug而且这种 bug 很隐蔽——代码能跑只是结果不对。我的经验是只在明确需要合并分支时使用穿透并且加上注释说明这是有意为之。4.3 现代语言里 switch 的演进值得一提的是很多现代语言对switch做了增强。Java 14 之后有了switch表达式可以直接返回值不用再写一堆breakPython 3.10 的match语句支持模式匹配能力比传统switch强很多JavaScript 的switch依然保持传统形态但实践中很多人用对象映射来替代它。以 JavaScript 为例下面这种写法在很多场景下比switch更简洁const actions { add: () console.log(执行添加), delete: () console.log(执行删除), update: () console.log(执行更新), query: () console.log(执行查询) }; const action add; if (actions[action]) { actions[action](); } else { console.log(未知操作); }这种用数据结构替代控制结构的思路在分支逻辑简单、分支数量多的时候特别好用。它把判断变成了查表代码更短扩展时只需要往对象里加一个键值对不用改判断逻辑。5. 条件逻辑的拆分从一坨代码到可维护结构5.1 什么时候该把条件抽成函数判断条件该不该抽成独立函数有个简单的标准如果这个条件表达式超过一行或者它表达了一个有业务含义的概念就该抽出来。举个例子判断一个用户是不是高价值活跃用户if user.order_count 10 and user.total_amount 5000 and user.last_active_days 7 and user.level 3: # 给优惠这个条件又长又有业务含义直接写在if里读代码的人得逐字分析才知道在判断什么。抽成函数def is_high_value_active_user(user): return (user.order_count 10 and user.total_amount 5000 and user.last_active_days 7 and user.level 3) if is_high_value_active_user(user): # 给优惠函数名本身就是文档主逻辑一眼就能看懂。而且这个判断如果在多个地方用到抽成函数还能避免重复。我个人的经验是业务规则类的条件一律抽函数纯技术性的简单判断比如判空、判类型留在原地。5.2 用策略模式替代膨胀的 if-else当if-else链的分支超过五六个而且每个分支做的事情还不少时就该考虑用策略模式了。所谓策略模式说白了就是把每个分支的处理逻辑封装成独立的函数或类然后用一个映射表来分发。还是用电商折扣举例。假设有普通用户、会员、超级会员三种每种有不同的折扣计算方式def normal_discount(amount): return amount * 0.95 def member_discount(amount): return amount * 0.90 def super_member_discount(amount): return amount * 0.85 discount_strategies { normal: normal_discount, member: member_discount, super_member: super_member_discount } def calculate_final_price(user_type, amount): strategy discount_strategies.get(user_type) if strategy is None: raise ValueError(f未知用户类型{user_type}) return strategy(amount)这样写的好处是新增用户类型只需要加一个函数和一个映射项不用动calculate_final_price的逻辑。每个折扣计算逻辑独立方便单独测试。这就是对扩展开放对修改关闭的思路。当然策略模式不是银弹。如果分支逻辑很简单比如就是返回不同的字符串那用if-else或switch反而更直接。过度设计和不设计一样有害判断标准是分支的复杂度和未来可能的扩展频率。5.3 条件表达式的可读性优化有些条件表达式本身没毛病但写出来就是难读。常见的有两类否定条件和德摩根定律的误用。否定条件的问题在于人脑处理不字比处理肯定句慢。比如if not user.is_disabled and not user.is_deleted读起来要在脑子里转两个弯。如果语言支持可以定义正向的辅助方法def is_active(user): return not user.is_disabled and not user.is_deleted if is_active(user): ...德摩根定律说的是not (A and B)等价于not A or not B。很多人在重构条件时会把前者改成后者觉得更简洁但实际上not (A and B)往往更符合人的直觉。比如if not (has_permission and is_owner)表达的是既不是有权限也不是所有者比if not has_permission or not is_owner更容易理解。不要为了炫技而改写条件以读起来顺畅为准。6. 那些年我在选择语句上踩过的坑6.1 浮点数比较等于判断的陷阱用比较浮点数是经典坑但在选择语句里特别容易中招。比如判断两个价格是否相等price_a 0.1 0.2 price_b 0.3 if price_a price_b: print(相等) else: print(不相等) # 实际会走这里因为浮点数精度问题0.1 0.2的结果是0.30000000000000004不等于0.3。涉及金额比较时正确做法是用Decimal类型或者判断差值是否小于一个极小值if abs(price_a - price_b) 1e-9: print(相等)这个坑我在做订单金额校验时踩过当时两个理论上相等的金额判断为不等排查了半天才发现是浮点精度问题。凡是涉及小数比较的条件判断一律用容差或定点数类型。6.2 短路求值的副作用大多数语言的和||都有短路特性左边为假就不算右边||左边为真就不算右边。这个特性可以用来做空值保护if user is not None and user.age 18: ...如果user是Noneuser.age根本不会执行不会报错。但反过来如果顺序写错if user.age 18 and user is not None: # 危险 ...user为None时第一个条件就会抛异常。这个坑的本质是没有利用短路特性做保护。我的习惯是在条件链里把可能为空的判断放在最前面。但短路也有副作用。如果右边的表达式有副作用比如函数调用会修改状态短路可能导致它不执行从而产生意料之外的行为。比如if is_valid() and save_record()如果is_valid()返回假save_record()就不会执行。这种写法本身就不推荐——条件判断里不应该有副作用判断归判断操作归操作。6.3 条件覆盖不全导致的幽灵分支有一种 bug 特别隐蔽某个条件分支在测试时从来没被触发过因为测试数据没覆盖到。比如if score 90: grade A elif score 80: grade B elif score 70: grade C elif score 60: # 注意这里是 而不是 grade D else: grade F如果测试时只测了 90、85、75、65、50那 60 分这个边界就漏了。60 分会落到else里变成 F但按常理 60 应该算及格。这种 bug 在代码审查时也很难发现因为逻辑看起来差不多对。避免这类问题的方法有两个一是写测试时专门针对每个边界值设计用例59、60、61 都要测二是用表格列出所有区间和预期结果逐行核对代码。我在做涉及等级、状态、分类的判断时都会先在纸上画一个区间表确认无重叠无遗漏之后再写代码。6.4 条件顺序引发的性能问题大多数情况下if-else链的性能差异可以忽略。但在热点代码里条件顺序会影响性能。原则是把最可能成立的条件放在最前面。比如判断一个请求的类型如果 90% 的请求都是查询那查询判断就该放第一个避免每次都走完整个链。不过我要强调的是不要为了微优化牺牲可读性。除非你确实通过性能分析确认这里是瓶颈否则按逻辑清晰度来排顺序就好。我见过有人为了优化把条件顺序打乱结果代码逻辑变得难以理解维护成本远超那点性能收益。7. 把选择语句练成真正的逻辑能力回到最开始的问题为什么很多人学完选择语句还是写不好业务逻辑因为他们练的是语法不是逻辑。语法是if (条件) { 操作 }这个形式逻辑是如何把现实规则翻译成条件判断这个能力。前者半小时能学会后者需要大量有质量的练习。我的建议是找几个你熟悉的真实场景——外卖满减、打车计费、会员等级、请假审批——把它们的规则用选择语句写出来。写完之后做三件事检查边界有没有遗漏看看嵌套能不能扁平化想想新增一条规则时改动大不大。这三件事做到位选择语句才算真正过关。最后分享一个我自己的习惯每次写完一段条件逻辑我会假装自己是三个月后接手这段代码的人从头读一遍。如果读的过程中需要停下来想这个条件为什么这么写那就说明需要加注释或者重构。代码是写给人看的顺便给机器执行——这个观念在选择语句上体现得尤其明显。