第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_idequal?观察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.encodingSTDOUT.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/inspectwarn默认写stderr,因此shell可以把正常数据和诊断分开。测试应分别捕获stdout与stderr,不要只观察终端混合后的画面。

数值表示与计算

整数支持任意精度语义,浮点数通常是IEEE 754 double。+ - * / % **分别表示加减乘除、余数与幂;整数相除5 / 2得到2,而只要一边是浮点数,5 / 2.0得到2.5。方法divfdivremainder%表达不同意图,负数边界必须用实例验证。

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条件中只有falsenil为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 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 2 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 3 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 4 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 5 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 6 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 7 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 8 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 9 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 10 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。
  • :第 11 个正式节点要能回到“同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。”,并说明故障发生前后的第一处差异。

对象与状态模型

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

怎样区分 Ruby 文件运行、命令行执行与交互环境显示的责任?

输入与接收者

固定Ruby的运行方法所需的原始值、Ruby 版本和调用入口。

状态变化

在执行前记录接收者身份,并声明ruby命令的执行方法的允许状态。

观察证据

保存第1章 Ruby初探的初值、参数、编码或资源位置。

正式节点:Ruby的运行方法、ruby命令的执行方法、irb命令的执行方法、对象与方法、字符串、puts方法与p方法、中文的输出、数值表示与计算、变量与注释、if~then~end、while语句与times方法

控制与消息轨迹

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

  1. 01固定Ruby的运行方法的输入和接收者
  2. 02执行ruby命令的执行方法并记录状态
  3. 03观察irb命令的执行方法的返回或副作用
  4. 04用while语句与times方法核对不变量并复位

运行不变量:同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。

边界故障探针

一次只破坏一个前提

基线满足:同一脚本、参数和 Ruby 版本产生可解释的输出、错误通道与退出状态。

本章回顾:运行、对象、观察、控制

  1. ruby执行可重复程序,irb额外显示表达式结果;-e只改变source入口,require负责按feature加载。
  2. 方法调用从receiver object开始,经method与arguments得到result;变量只保存对象引用并可rebind。
  3. 双引号支持escape与interpolation,中文输出还受String与外部编码影响。
  4. puts面向正常文本,p面向结构诊断,warn写stderr;三者不是序列化协议。
  5. 数值operator要先明确整数、浮点和业务domain;除法与近似值边界必须测试。
  6. ifwhiletimes分别表达分支、状态循环与固定次数;只有false和nil为falsey。

练习与答案

练习

  1. 问题 1:建立正常轨迹。 回答“怎样区分 Ruby 文件运行、命令行执行与交互环境显示的责任?”,并写出四步执行记录。
  1. 问题 2:注入单一故障。 只制造“把交互环境自动显示的表达式结果误当成脚本输出”,应从哪里开始定位?
  1. 问题 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初探”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。

资料与写作方式声明

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

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

讨论

评论区加载中…