第5章 条件判断

建立Ruby的truth contract,掌握逻辑运算符的operand返回、if/unless/case表达式、case equality以及条件修饰符的边界。

学习目标

  • 能解释Ruby中的条件与逻辑运算符在“第5章 条件判断”中的责任边界
  • 能围绕“怎样用边界表证明 Ruby 条件、逻辑运算与分支顺序覆盖完整输入域?”运行正常与故障轨迹并定位首个分岔
  • 能用“第5章 条件判断的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“任一合法输入进入唯一预期分支,nil、false、0 与空容器不被混淆。”

来源、版次与运行边界

“第5章 条件判断”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单练习答案勘误只作为公开支持材料,不被冒充为原书全文。

对“第5章 条件判断”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。

“第5章 条件判断”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。

围绕“怎样用边界表证明 Ruby 条件、逻辑运算与分支顺序覆盖完整输入域?”,本页验收“第5章 条件判断的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“把 0 或空字符串当作假值,导致合法输入进入拒绝分支”;若无法定位第一处状态分岔,就拒绝当前解释。

从“值为空就是假”这个迁移陷阱开始

Ruby中的条件不是自动调用empty?zero?或“是否存在内容”;它只把nilfalse视为falsey,其余对象全部truthy。必须与业务validity分开。

先预测:if []if ""if 0分别走哪条分支?全部走true branch。若要判断集合有元素,调用any?empty?;判断数字非零,调用zero?;判断文本有效,按schema执行strip、长度和格式校验。

什么是条件判断

条件判断把一个value解释为truthy/falsey,再选择执行路径。每个分支还应说明输出、side effect与错误owner。让分支从语法变成可测试的policy。

ifunlesscase都是expression:它们返回被执行分支最后一个表达式的值;没有分支命中且无else时返回nil。这允许直接赋值,但嵌套过深会隐藏控制流。先让条件名称表达业务问题,再决定是否内联。

条件还不能吞掉“无法判断”。例如权限查询可能得到allow、deny或backend unavailable;把异常rescue成false会让安全策略看似保守,却丢失告警与重试信息。先建decision table:列出输入状态、selected branch、返回值、side effect、日志和错误传播。只有真正的二值domain才压缩成boolean;三态或多态结果使用Symbol、value object或明确异常。

重复访问会变化的外部状态也会破坏分支推理,例如先exists?再读取文件存在time-of-check/time-of-use race。尽量执行目标操作并处理结果,而不是用一个脆弱condition预判。测试条件时除了每支happy path,还要覆盖边界值、重叠predicate、异常和未命中路径。

Ruby中的条件

nil常表达absence,false表达否定,但两者在condition中相同,在数据层却必须区分。配置项允许false时,configured || default会错误覆盖false。应写成显式nil判断。

def resolve_enabled(configured, default: true)
  configured.nil? ? default : configured
end
 
p resolve_enabled(nil)   # true
p resolve_enabled(false) # false
p resolve_enabled(0)     # 0; schema may reject it later

Predicate method惯例以?结尾并返回truthy/falsey;最佳实践通常返回true/false,避免泄漏任意对象。组合复杂条件前先把输入normalize,例如将用户文本转换成enum,再判断enum;不要在每个branch重复strip/downcase和nil处理。

逻辑运算符

&&/and||/or都会short-circuit并返回某个operand,不强制转成boolean。!/not返回boolean。符号版本与单词版本的precedence不同:&&高于||,而and/or优先级很低,常用于控制流但容易与assignment组合出意外。

token = nil
fallback = "guest"
identity = token || fallback
 
ready = identity && !identity.empty?
p identity: identity, ready: ready
 
# 用括号表达政策,不依赖读者背precedence
allowed = authenticated? && (admin? || owns_resource?)

Short-circuit可做guard,例如user && user.name,但modern Ruby可用safe navigation user&.name表达nil propagation。不要在右operand隐藏关键side effect,如valid? && charge!;失败原因、重试和审计都需要显式branch。

要求测试不仅看boolean,还要看哪些method被调用以及返回了哪个对象。

if语句

if condition ... elsif ... else ... end按顺序测试,第一条truthy branch获胜。把最具体或最便宜条件放前面并不总是正确;ordering本身是policy,例如授权先验证identity,再判断role,不能为性能交换到会泄漏信息的顺序。

label = if score.nil?
  "missing"
elsif score >= 90
  "excellent"
elsif score >= 60
  "pass"
else
  "retry"
end

多个branch返回不同shape会把复杂性推给caller。上例都返回String;若一支返回nil、一支返回Hash,应先设计result type。复杂predicate提取为命名method,但不要把每一行都拆成跳转,关键是让branch decision contract一眼可读。

unless语句

unless condition相当于if !condition,适合简单negative guard:return unless enabled?。带else的unless虽然合法,却要求读者对两个分支做双重否定;有else时通常改写为positive if。限制unless的使用范围。

unless !ready?unless a && b和嵌套unless都应重写为命名positive predicate。代码审查不只确认结果等价,还要确认错误信息和early return顺序未改变。

case语句

case value; when candidate并非固定用==,而是计算candidate === value。Literal通常像value equality,Range检查包含关系,Regexp执行匹配,Class/Module检查is_a?关系。解释了Ruby case的扩展能力。

def classify(value)
  case value
  when 200..299 then :success
  when 400..499 then :client_error
  when /\Auser:/ then :user_record
  when String then :other_text
  else :unknown
  end
end

Ordering决定结果:Regexp branch必须放在宽泛String branch之前,否则永远到不了;overlapping Range也由先出现者获胜。case无expression时可写case; when predicate ...,但本质接近elsif chain;只有候选共享同一被测value并且===清楚时,value case最有优势。

if修饰符与unless修饰符

publish(record) if record.valid?适合一个短动作和一个显然predicate;修饰符仍先按照Ruby grammar解析整个statement,再按condition决定执行。Assignment、rescue modifier或多段method chain与条件修饰符组合,会让precedence难读,应改为block form。

修饰符不适合隐藏主要成功路径,也不应承载多步resource lifecycle。例如file.close if opened不如block-form open可靠,后者对异常也保证cleanup。可读性判断包括line长度、side effect重要性、是否需要else和debug时是否容易加观测点。

正式节点与章专属证据

  • :第 1 个正式节点要能回到“任一合法输入进入唯一预期分支,nil、false、0 与空容器不被混淆。”,并说明故障发生前后的第一处差异。
  • :第 2 个正式节点要能回到“任一合法输入进入唯一预期分支,nil、false、0 与空容器不被混淆。”,并说明故障发生前后的第一处差异。
  • :第 3 个正式节点要能回到“任一合法输入进入唯一预期分支,nil、false、0 与空容器不被混淆。”,并说明故障发生前后的第一处差异。
  • :第 4 个正式节点要能回到“任一合法输入进入唯一预期分支,nil、false、0 与空容器不被混淆。”,并说明故障发生前后的第一处差异。
  • :第 5 个正式节点要能回到“任一合法输入进入唯一预期分支,nil、false、0 与空容器不被混淆。”,并说明故障发生前后的第一处差异。
  • :第 6 个正式节点要能回到“任一合法输入进入唯一预期分支,nil、false、0 与空容器不被混淆。”,并说明故障发生前后的第一处差异。

对象与状态模型

从输入、接收者到可观察证据

怎样用边界表证明 Ruby 条件、逻辑运算与分支顺序覆盖完整输入域?

输入与接收者

固定Ruby中的条件所需的原始值、Ruby 版本和调用入口。

状态变化

在执行前记录接收者身份,并声明逻辑运算符的允许状态。

观察证据

保存第5章 条件判断的初值、参数、编码或资源位置。

正式节点:Ruby中的条件、逻辑运算符、if语句、unless语句、case语句、if修饰符与unless修饰符

控制与消息轨迹

在相同初值下定位首个分岔

  1. 01固定Ruby中的条件的输入和接收者
  2. 02执行逻辑运算符并记录状态
  3. 03观察if语句的返回或副作用
  4. 04用if修饰符与unless修饰符核对不变量并复位

运行不变量:任一合法输入进入唯一预期分支,nil、false、0 与空容器不被混淆。

边界故障探针

一次只破坏一个前提

基线满足:任一合法输入进入唯一预期分支,nil、false、0 与空容器不被混淆。

本章回顾:语言真假不等于业务有效

  1. 只有nil和false为falsey;0、空String和空collection都truthy,业务规则使用专门predicate。
  2. &&/|| short-circuit并返回operand;false可用配置不能直接套value || default
  3. if按序选择第一条truthy branch并返回结果;unless只用于简单negative guard。
  4. case调用candidate的===,可由literal、Range、Regexp与Class提供不同matching semantics。
  5. 条件修饰符只承载短小清晰动作;复杂precedence、resource与重要side effect用block form。

练习与答案

练习

  1. 问题 1:建立正常轨迹。 回答“怎样用边界表证明 Ruby 条件、逻辑运算与分支顺序覆盖完整输入域?”,并写出四步执行记录。
  1. 问题 2:注入单一故障。 只制造“把 0 或空字符串当作假值,导致合法输入进入拒绝分支”,应从哪里开始定位?
  1. 问题 3:覆盖正式节点。 用一个证据包串联Ruby中的条件、逻辑运算符、if语句、unless语句、case语句、if修饰符与unless修饰符,说明为什么结论可由另一位读者独立复核。

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

Ruby中的条件

“第5章 条件判断”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

逻辑运算符

“第5章 条件判断”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

if语句

“第5章 条件判断”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

unless语句

“第5章 条件判断”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

case语句

“第5章 条件判断”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

if修饰符与unless修饰符

“第5章 条件判断”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

资料与写作方式声明

本章以Ruby 基础教程第 5 版作者支持资料合法公开试读核定可见范围,并以目录限定未公开部分,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…