第1章 Ruby初探
从ruby与irb两种运行方式出发,建立对象接收消息的执行模型,掌握字符串、输出、数值、变量、注释以及if、while、times的最小程序闭环。
从一次可重复运行开始: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。能把“看起来会停”改成可检查的状态关系。
本章回顾:运行、对象、观察、控制
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。