第1章 Ruby初探
从ruby与irb两种运行方式出发,建立对象接收消息的执行模型,掌握字符串、输出、数值、变量、注释以及if、while、times的最小程序闭环。
学习目标
- 能解释Ruby的运行方法与ruby命令的执行方法在“第1章 Ruby初探”中的责任边界
- 能围绕“怎样区分 Ruby 文件运行、命令行执行与交互环境显示的责任?”运行正常与故障轨迹并定位首个分岔
- 能用“第1章 Ruby初探的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”
来源、版次与运行边界
“第1章 Ruby初探”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单、练习答案和勘误只作为公开支持材料,不被冒充为原书全文。
对“第1章 Ruby初探”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。
“第1章 Ruby初探”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档与稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。
围绕“怎样区分 Ruby 文件运行、命令行执行与交互环境显示的责任?”,本页验收“第1章 Ruby初探的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“把交互环境自动显示的表达式结果误当成脚本输出”;若无法定位第一处状态分岔,就拒绝当前解释。
从一次可重复运行开始:Ruby的运行方法
学习Ruby的第一个障碍不是语法,而是没有区分“程序产生了输出”和“交互环境替你显示了表达式的值”。把1 + 2输入IRB会看到=> 3,因为IRB执行read-eval-print loop;把同样一行保存到hello.rb后运行,程序正常结束,却不会自动显示3。要求每次实验先写明由谁读取source、谁显示结果、谁决定进程退出状态。
# hello.rb
name = ARGV.fetch(0, "Ruby")
puts "Hello, #{name}!"
p({ ruby: RUBY_VERSION, argument_count: ARGV.length })运行ruby hello.rb Matz会由程序明确输出问候与诊断Hash。ARGV保存脚本参数,不包含Ruby可执行文件和脚本名;fetch(0, "Ruby")在没有第一个参数时给出默认值。真正可复现的最小记录应包括Ruby版本、运行命令、当前目录、输入参数、标准输出、标准错误和exit status。
先预测:ruby -e '1 + 2'会不会显示3?不会,因为-e只提供source,不改变普通文件模式的输出规则;写成ruby -e 'p 1 + 2'才显示。这个小差异能发现大量“在IRB能运行、放进脚本却什么也没有”的误判。
ruby命令的执行方法与irb命令的执行方法
ruby file.rb适合完整程序;ruby -c file.rb只检查语法,不执行顶层副作用;ruby -w启用常见warning;ruby -Ilib把目录加入load path。IRB适合探索对象和方法,但历史状态会污染实验:前一条定义的局部变量、方法或被加载的库仍然存在。遇到可疑结果应重启IRB,或者把最小复现写成独立文件。
load "config.rb"每次都会重新执行文件,require "json"通常每个feature只加载一次;它们不是等价的“引用文件”。本章先记住边界,第3章再把命令与文件读取连成完整工具。不要让业务逻辑依赖当前目录偶然存在某个文件,路径归属必须明确。
程序解说:对象与方法
Ruby把字符串、整数、数组、类乃至方法返回值都作为对象处理。表达式"ruby".upcase可读成:以字符串对象为receiver,发送upcase方法消息,得到新的"RUBY"对象。比“一切皆对象”的口号更有操作性,因为调试时要同时回答receiver是什么、method是什么、arguments是什么、result是什么。
word = "Ruby"
length = word.length
upper = word.upcase
p word: word, length: length, upper: upper
p word.class # String
p length.class # Integer
p word.equal?(upper) # false: upcase返回另一个对象括号常可省略,例如puts("hello")与puts "hello"都合法;但在嵌套调用或参数边界不清时保留括号。object_id和equal?观察identity,==通常观察value equality。不要因为两个字符串内容相等就假定它们是同一个对象,也不要在业务代码里把object_id当持久标识。
字符串与中文的输出:引号、插值、换行和编码
双引号字符串处理\n等escape并执行#{...}插值;单引号字符串只对少数escape特殊处理,不执行插值。把数据和展示模板连接起来,但不能替代SQL参数、shell escaping或HTML escaping。
language = "Ruby"
version = RUBY_VERSION
puts "#{language} #{version}\nready"
puts '#{language} is not expanded'
puts "2 + 3 = #{2 + 3}"Source文件按目标Ruby与编辑器约定保存为UTF-8,现代Ruby通常默认UTF-8 source encoding;第19章会系统处理Encoding。所谓“中文的输出”至少跨越source bytes、String encoding、stdout external encoding与终端字体四层。出现乱码时记录__ENCODING__、string.encoding和STDOUT.external_encoding,不能只反复改字面量。
puts方法与p方法:面向用户和面向诊断
puts调用对象的字符串表示并追加换行,数组会逐元素输出;p输出更接近inspect,保留引号、escape和容器结构,并返回被输出对象。适合开发日志,却不保证是稳定序列化格式。
value = "line 1\nline 2"
items = ["a", "b"]
puts value
p value
puts items
p items
warn "diagnostic goes to stderr"面向用户的CLI输出使用明确格式;机器接口使用JSON、CSV等协议;调试才依赖p/inspect。warn默认写stderr,因此shell可以把正常数据和诊断分开。测试应分别捕获stdout与stderr,不要只观察终端混合后的画面。
数值表示与计算
整数支持任意精度语义,浮点数通常是IEEE 754 double。+ - * / % **分别表示加减乘除、余数与幂;整数相除5 / 2得到2,而只要一边是浮点数,5 / 2.0得到2.5。方法div、fdiv、remainder与%表达不同意图,负数边界必须用实例验证。
Math.sqrt(9)返回3.0;调用Math模块方法通常使用Math.。先决定结果domain再选operator:金额不要直接依赖binary float,计数不要意外做整数截断,除数来自输入时先验证零。第12章会展开Numeric层次、随机数、bit operation与近似值误差。
subtotal = 1250
tax_rate = 0.10
total = subtotal + (subtotal * tax_rate).round
p integer_division: 5 / 2
p floating_division: 5.fdiv(2)
p square_root: Math.sqrt(9)
p total: total变量与注释:名字引用对象
局部变量名通常以小写字母或下划线开头;赋值让名字引用一个对象,之后可以rebind到另一个class的对象。说明Ruby是动态类型,但不表示接口无需约束。边界仍需验证输入类型、可调用方法和业务不变量。
#到行末是comment。Comment应解释约束、原因或非显然选择,不应逐字翻译代码。特殊位置的encoding comment、shebang或工具directive有语义,不是普通说明。删除一条comment前先判断它是否被Ruby、shell或工具读取。
赋值有返回值,因此能嵌入表达式,但if item = lookup容易与==混淆并触发warning。Production代码优先分成“获得值”和“判断值”两步,使nil、false和错误处理清楚可测。
条件判断:if~then~end
Ruby条件中只有false和nil为falsey,0、空字符串与空数组都truthy。if本身是expression,会返回执行分支最后一个表达式的值。第一次练习可以使用if ... then ... end,实际代码常省略then并用换行建立结构。
count = Integer(ARGV.fetch(0, "3"), 10)
if count.negative?
warn "count must be non-negative"
exit 2
end
count.times do |index|
puts "step #{index + 1}/#{count}"
end循环:while语句与times方法
while适合终止条件依赖不断变化状态的循环;必须能证明每次迭代向终止前进。times由Integer receiver决定固定次数,并把从0开始的index传给block。能把“看起来会停”改成可检查的状态关系。
正式节点与章专属证据
- ↡在“第1章 Ruby初探”中,Ruby的运行方法必须连接输入、状态变化与可复核结果。 :第 1 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,ruby命令的执行方法必须连接输入、状态变化与可复核结果。 :第 2 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,irb命令的执行方法必须连接输入、状态变化与可复核结果。 :第 3 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,对象与方法必须连接输入、状态变化与可复核结果。 :第 4 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,字符串必须连接输入、状态变化与可复核结果。 :第 5 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,puts方法与p方法必须连接输入、状态变化与可复核结果。 :第 6 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,中文的输出必须连接输入、状态变化与可复核结果。 :第 7 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,数值表示与计算必须连接输入、状态变化与可复核结果。 :第 8 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,变量与注释必须连接输入、状态变化与可复核结果。 :第 9 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,if~then~end必须连接输入、状态变化与可复核结果。 :第 10 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
- ↡在“第1章 Ruby初探”中,while语句与times方法必须连接输入、状态变化与可复核结果。 :第 11 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
对象与状态模型
从输入、接收者到可观察证据
怎样区分 Ruby 文件运行、命令行执行与交互环境显示的责任?
输入与接收者
固定Ruby的运行方法所需的原始值、Ruby 版本和调用入口。
状态变化
在执行前记录接收者身份,并声明ruby命令的执行方法的允许状态。
观察证据
保存第1章 Ruby初探的初值、参数、编码或资源位置。
正式节点:Ruby的运行方法、ruby命令的执行方法、irb命令的执行方法、对象与方法、字符串、puts方法与p方法、中文的输出、数值表示与计算、变量与注释、if~then~end、while语句与times方法
控制与消息轨迹
在相同初值下定位首个分岔
- 01固定Ruby的运行方法的输入和接收者
- 02执行ruby命令的执行方法并记录状态
- 03观察irb命令的执行方法的返回或副作用
- 04用while语句与times方法核对不变量并复位
运行不变量:同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。
边界故障探针
一次只破坏一个前提
本章回顾:运行、对象、观察、控制
ruby执行可重复程序,irb额外显示表达式结果;-e只改变source入口,require负责按feature加载。- 方法调用从receiver object开始,经method与arguments得到result;变量只保存对象引用并可rebind。
- 双引号支持escape与interpolation,中文输出还受String与外部编码影响。
puts面向正常文本,p面向结构诊断,warn写stderr;三者不是序列化协议。- 数值operator要先明确整数、浮点和业务domain;除法与近似值边界必须测试。
if、while与times分别表达分支、状态循环与固定次数;只有false和nil为falsey。
练习与答案
练习
- 问题 1:建立正常轨迹。 回答“怎样区分 Ruby 文件运行、命令行执行与交互环境显示的责任?”,并写出四步执行记录。
- 问题 2:注入单一故障。 只制造“把交互环境自动显示的表达式结果误当成脚本输出”,应从哪里开始定位?
- 问题 3:覆盖正式节点。 用一个证据包串联Ruby的运行方法、ruby命令的执行方法、irb命令的执行方法、对象与方法、字符串、puts方法与p方法、中文的输出、数值表示与计算、变量与注释、if~then~end、while语句与times方法,说明为什么结论可由另一位读者独立复核。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- Ruby的运行方法
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- ruby命令的执行方法
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- irb命令的执行方法
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 对象与方法
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 字符串
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- puts方法与p方法
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 中文的输出
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 数值表示与计算
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 变量与注释
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- if~then~end
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- while语句与times方法
“第1章 Ruby初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。