〔连载〕Programming Ruby中文版:第2版
ruby吧
全部回复
仅看楼主
level 6
mew~mew~ 楼主
本贴需要看CSDN会不会继续连载再发了...
2010年01月04日 11点01分 1
level 6
mew~mew~ 楼主
而且我也非常希望能得到你的反馈。欢迎任何评论,提出建议,以及对书中的错误和例子中的问题给予指正。请发邮件到:
[email protected]
如果你告诉我们书中的错误,我会将它们加入到位于以下地址的勘误表中:
http://www.pragmaticprogrammer.com/titles/ruby/errata.html
从下面的地址中你会发现书中几乎所有样例代码的源代码链接:
http://www.pragmaticprogrammer.com/titles/ruby
致谢
Acknowledgments
我曾在Ruby邮件列表中询问是否有人愿意帮助审查此书的第2版。几乎有一百名志愿者响应了我的请求。为了便于管理,我不得不依据先来先得的原则限制人数。即使这样,那些出色的审查员还是给了我超过1.5MB的建议。他们指出了许多问题,从放错地方的逗号到遗漏的方法。我不可能获得比这更好的帮助了,所以非常“感谢”RichardAmacker, David A. Black, Tony Bowden, James Britt, Warren Brown,Mike Clark, Ryan Davis (感谢那个日文PDF!),Guy Decoux, Friedrich Dominicus,Thomas Enebo, Chad Fowler, HalFulton, Ben Giddings, Johan Holmberg, Andrew Johnson, Rich Kilmer,Robert Klemme,Yukihiro Matsumoto, Marcel Molina Jr., Roeland Moors, Michael Neumann,Paul Rogers,Sean Russell, Hugh Sasse, Gavin Sinclair, Tanaka Akira, Juliet Thomas,GlennVanderburg, Koen Vervloesem和Austin Ziegler.
Chad Fowler写了RubyGems一章。实际上,他写了两次。写第一次的时候他在欧洲休假,不幸在回家的路上,他的Powerbook被偷了,而他所写的书稿也丢失了,所以回去以后,他只好又坐下来重写了一遍。对此我感激不尽。
Kim Wimpsett做了编辑这一费力不讨好的工作。她做了大量的工作(而且超过了记录),而本书中行业术语的数量和我对语言的组织能力之差使得这项任务更加困难。Ed Giddens设计了出色的封面,该封面极好地混合了新老封面。谢谢他们!
最后,我仍想对Ruby的创建者Yukihiro“Matz” Matsumoto致以深深的谢意。在这段成长和变化的时间内,他一直以快乐和专一的精神来改进Ruby语言。Ruby社区的友善和开发精神是他个人精神的直接体现。
谢谢各位。Domo arigato gozaimasu.
Dave Thomas
THE PRAGMATICPROGRAMMERS
http://www.pragmaticprogrammer.com
符号约定
Notation Conventions
在本书中,我们使用如下排版符号。
代码样例使用等宽字体显示。
class SampleCode
defrun
#...
end
end
在正文中,Fred#do_something是对Fred类的实例方法(这里是do_something)的引用,Fred.new是一个类方法,而Fred::EOF是一个类常量。使用井号符来表示实例方法的决定是很棘手的:它不是合法的Ruby语法,但是我们认为区分一个类的实例方法和类方法是很重要的。当你看到File.read时,你知道我们在讨论类方法read。而当你看到File#read时,我们在引用实例方法read。
本书包含了很多Ruby代码片断。如果可能,我们会尽量显示运行它们的结果。一个最简单的例子,我们将表达式的值和表达式显示在同一行上。例如:
a = 1
b = 2
a + b    →     3
在这里,你能看到 a + b 的计算结果3显示在了箭头的右边。注意如果你只是运行这个程序,那么你不会看到输出结果3——你需要使用类似 puts 这样的方法将其输出。
有时我们对赋值语句的值感兴趣,这种情况下我们也会把它显示出来。
a = 1    →     1
b = 2    →     2
a + b    →     3
如果程序输出更复杂,那么我们将其显示在程序代码下面。
3.times { puts"Hello!" }
输出结果:
Hello!
Hello!
Hello!
在某些库文档中,我们需要在输出中显示空格。你会看到空格被表示成“ ”字符。
命令行调用使用等宽字体表示,而参数使用斜体表示。可选元素被放在中括号中。
ruby [ flags ... ] [ progname ] [arguments ... ]

2010年01月04日 11点01分 3
level 6
mew~mew~ 楼主
人受创造之驱动;我清楚自己非常喜欢创造。尽管我不善于油画、素描或者音乐,但是我可以写软件。
在接触计算机后不久,我就对编程语言产生了浓厚的兴趣。我坚信一定可以实现一种理想的编程语言,而我希望能成为其设计者。后来在积累了一些经验之后,我意识到设计这种理想的、通用的语言比我想象的要难得多,但是我依旧希望能设计一种能解决我日常遇到的大多数问题的语言,这是我学生时代的梦想。
多年后,我和同事谈起了脚本语言以及它们的处理能力和潜力。作为一个超过15年的面向对象的爱好者,我认为面向对象编程也非常适合脚本语言。我在网络上做了一段时期的调查,但是我找到的候选,例如Perl和Python,都不是我真正需要的。我需要一个比Perl更强大、比Python更面向对象的语言。
后来,我想起了以前的梦想,于是决定自己设计一种语言。起初我只是在工作时用来自娱。但是,逐渐地它成长为一个足以替代Perl的工具。我命名为Ruby —— 先前的名字叫Red Stone —— 并在1995年公开发布。
从那时起,越来越多的人开始对Ruby感兴趣。不管你相信与否,现在Ruby在日本实际上已经比Python更流行了。我希望最终它能在全世界被广泛地接受。
我认为生活的目的是快乐,至少部分是快乐。基于这种信念,Ruby被设计成一种使编程不但容易而且有趣的语言。它能让你面对更少的压力,集中精力于程序设计中的有创造性的一面。如果你不相信我,请阅读此书并试用一下Ruby。我确信你自己会体会到这一点。
我非常感谢那些参加Ruby社区的人;他们给了我莫大的帮助。我总是觉得Ruby是我的一个孩子,但实际上它是许多人共同努力的结晶。没有他们的帮助,Ruby不可能发展成现在这样。
我要特别感谢此书的作者,Dave Thomas和Andy Hunt。Ruby一直不是一个文档齐全的语言。因为我喜欢写程序胜于写文档,所以Ruby手册没有应有的那样完善。你必须通过阅读源代码来获悉语言的准确行为。但是现在Dave和Andy为你做了这一工作。
他们对来自远东的一个不怎么出名的语言产生了兴趣。他们研究它,阅读了成千上万行源代码,编写了不计其数的测试脚本和电子邮件,澄清了语言的某些不明确的行为,发现了许多bug(甚至修正了其中的许多),并最终编写了这本出色的书。毫无疑问,Ruby因此已经成了一个文档完善的语言。
他们在本书上花的精力绝不是微不足道的。当他们在写此书时,我对语言本身进行了一些修改。我们一起为这些更新而工作,并使此书尽量准确。
我希望Ruby和本书能使你编程更容易和有趣。工作得开心!
YukihiroMatsumoto,a.k.a. “Matz”
まつもとゆきひろ
2000年10月于日本

2010年01月04日 11点01分 4
level 6
mew~mew~ 楼主
根据我的观察,习惯于Java或者C#的程序员在初初接触Ruby时,最能打动他们的往往就是像本文标题这样的一句代码:原本熟悉的字符串或者整数突然摇身一变,有了很多新的行为,甚至让整个Ruby语言都似乎变了个样。尽管“改变标准库的行为”并不总是值得推荐的做法,但如果使用得当,你能够在Ruby的基础上创造出一种贴近项目需求、易写易读的方言——也有人把这些方言叫做“领域专用语言”(DSL,DomainSpecificLanguage)。更多的程序员是因为Rails这个框架才开始对Ruby语言产生兴趣,而Rails在很大程度上正是一种针对Web应用开发的DSL。
能够创造DSL,这是Ruby语言最大的魅力之一。但仅仅这一点并不足以解释为何有那么多优秀的程序员如此盛赞Ruby语言,更不足以解释为何它会突然间红透半边天——毕竟,在元编程方面更具实力的LISP和Smalltalk并没有像如今的Ruby这样流行。作为一个Java程序员的MikeClark给了我们一个有趣的比喻:推绳子——他说“读了仅仅几页 ProgrammingRuby之后,再使用Ruby之外的语言编程感觉就像是在‘推绳子’(pushrope)。”把一根软绵绵的绳子往前推,那种有劲使不上的感觉,正是用惯Ruby之后再回到Java/C#时的真实感受。
灵活、优雅、巧妙、便利……这些溢美之词我们已经听得太多了。但在我看来,Ruby最大的特点就一个字:快。这不仅意味着你能够很快地为自己的问题找到现成的解决办法,更意味着你能够直观地描述自己心中的想法,并且在改变想法时能够很快地调整你的程序。这种能力对于今天的软件开发者而言显得尤为重要,因为世界在飞快地改变,软件项目的需求在飞快地改变。对于今天的软件客户来说,尽快得到可以工作的软件、尽快反馈、尽快看到调整的效果,比一个完美但尚未实现的设计要有价值得多。而Ruby这种“快速实现想法”的能力,正是众多开发者对之青睐有加的根源所在。
Ruby能够帮你描述心中所想——这句话,在某种意义上,也意味着你需要熟悉Ruby的思考方式。尽管自称是面向对象的脚本语言,Ruby的精神仍然与函数式编程(functionalprogramming)一脉相承。这种精神不仅体现在语法层面上,还体现在构建系统的思路上。Ruby社群很少会一开始就把要实现的目标想得清清楚楚,或是首先制定种种规范标准;相反,他们会充分利用Ruby的灵活与简洁、优雅与巧妙,从一个简单的、能够工作的软件开始,逐步增加更多的功能,并通过不断重构和优化让良好的设计逐渐浮现。
是以,跨进Ruby的世界,也许你首先需要学会的是这种“渐进式”的思维方式——不仅仅是编写软件,就连“学习Ruby语言”本身也一样。你无须读18本书或者参加半年培训来学会Ruby编程——另一方面即便你这么做了也未必就能学会,如果你没有使用Ruby来编写真正有用的程序的话。所以,如果你对Ruby产生了兴趣,稍微了解一下,然后就开始写吧:把编写shell脚本的首选语言从Perl改为Ruby,用Rake来构建你的项目,或者——像大多数人那样——用Rails来开发一个小网站。你会遇到无数的问题,解决这些问题的过程就是对你的技术进行重构的过程。
但你至少还需要通过某种途径来“稍微了解”Ruby语言,而且在遇到问题时也需要一本手册来帮你排疑解难。在你手上的这本Programming Ruby正是为此而出的一本书。书中的精彩内容无须我在这里赘述,你大可以自己去发掘。我唯一想要告诉你的是:如果你想要开采最瑰丽的“红宝石”宝藏,这本书就是你不可或缺的“镐头”。锻造这柄镐头的是两位大名鼎鼎的“实用主义程序员”Dave Thomas和AndyHunt,这两位撰写过一系列C++/Java/.NET技术图书的开发者最终选择用Ruby on Rails来开发他们自己的网站(PragmaticProgrammer.com),本身就已经证明了Ruby的价值,同时也让我们对本书的实用性更有信心。
所以,你还在犹豫什么呢?既然已经拿起了这本书,既然已经对Ruby产生了兴趣,就不要再浪费时间了。翻开书,跟着这两位讲求实效的作者一道,现在就开始你的Ruby编程之旅吧。Ruby已经向你说过“hello”了,你将会如何回应它呢?
最后,和以往一样,祝你在Ruby的世界里,编程快乐!
熊 节
ThoughtWorks咨询师
2006年12月于西安

2010年01月04日 11点01分 8
level 6
mew~mew~ 楼主
关于Ruby语言及相关技术,非常感谢孟岩兄和熊节兄在前面所做的精辟入里的分析与推荐,这里就不再赘述了。相信您读了之后,一定是已经怦然心动而跃跃欲试了。
借此机会,我们还要感谢许多人。
首先要感谢博文视点公司引进此书,并将如此重要的一部书交托给我们来翻译,希望能幸不辱命。也要感谢本书的两位编辑方舟和陈元玉,没有二位认真、辛苦的工作,本书不可能有现在的翻译质量。我们也从他们的技术和文字校订中学到了很多。
另外译者也要相互感谢一下。在本书翻译的四个月期间,姚延栋刚做了父亲,张海峰也是家有幼子,并开始了新的职业征途,但两位都竭力保证了翻译的进度和质量。孙勇则不辞辛苦地对全书进行了统稿。当然,一定要感谢家人对我们的宽容和理解。
由于译者对Ruby语言也是新学,水平有限,难免在译稿中存有这样那样的错误。如果您有技术或文字方面的问题,欢迎致信[email protected],我们会尽力帮您解答。
译 者
2006年12月于北京

2010年01月04日 11点01分 9
level 6
mew~mew~ 楼主
2.2 Ruby的一些基本知识
Some Basic Ruby
开始学习一种新语言时没有多少人喜欢阅读成堆的、令人心烦的语法规则,所以我们暂时绕开一下。本节会讲到一些重要的规则,这些规则是编写Ruby程序所必须掌握的。在后面的章节中(第22章,从第317页开始),我们再介绍全部细节。
让我们从一个简单的Ruby程序开始。编写一个方法,让它返回一个亲切的、个性化的问候。然后我们会调用这个方法若干次。
defsay_goodnight(name)
result = "Good night, " + name
return result
end
# time for bed…
puts say_goodnight("John-Boy")
puts say_goodnight("Mary-Ellen")
就像这个范例所显示的那样,Ruby语法干净。只要每个语句放在单独的行上,就不需要在语句结束处加上分号。Ruby注释以一个#字符开始,在行尾结束。代码布局任由你决定;缩进编排并不重要(但是如果打算要发布你的代码的话,使用两个字符的缩进编排会让你在社区内交很多朋友)。
方法(method)用关键字def来定义,后面跟着方法名称(在本例中,它是say_goodnight)和在括号中的方法参数(实际上,括号是可选的,但我们喜欢使用它们),Ruby没有使用花括号来界定复杂的语句和定义的程序体。相反,可以用关键字end结束这个程序体。这个方法的程序体相当地简单。第一行连接"Goodnight,"字符串和参数name,并把这个结果赋值给局部变量result。下一行将结果返回给调用者。注意,我们不必声明result这个变量:当赋值给它时,它便存在了。
这个方法在定义之后被调用了两次,两次都是把结果传递给puts方法。puts的功能就是输出其参数,后面跟一个回车换行符(移动到下一行)。
Good night, John-Boy
Good night, Mary-Ellen
puts say_goodnight("John-Boy")这行代码包含两个方法调用,一个是say_goodnight,另外一个是puts。为什么一个调用把参数放在括号里,而另外一个没有呢?这纯粹是个人的喜好。下面两行是等同的。
puts say_goodnight("John-Boy")
puts(say_goodnight("John-Boy"))
当然,生活不是总这么简单的,优先级规则使得我们难以判断哪个参数属于哪个方法调用。所以建议除了最简单的情况以外,都请使用括号。
这个范例也展示了一些Ruby字符串对象。创建字符串对象有多种途径,最常用的可能是使用字符串字面量(literals),即一组单引号或双引号之间的字符序列。这两种形式的区别在于,当构造字面量时,Ruby对字符串所做处理的多寡有所不同。Ruby对单引号串处理得很少。除了极少的一些例外,键入到字符串字面量的内容就构成了这个字符串的值。
Ruby对双引号字符串有更多的处理。首先,它寻找以反斜线开始的序列,并用二进制值替换它们。其中最常见的是\n,它会被回车换行符替换掉。当一个包含回车换行符的字符串输出时,\n会强制换行。
puts "And good night,\nGranma"
输出结果:
And good night,
Grandma
Ruby对双引号字符串所做的第二件事情是字符串内的表达式内插(expression interpolation),#{表达式}序列会被“表达式”的值替换。可以用这种方式重写前面的方法。
defsay_goodnight(name)
result = "Good night, #{name}"
return result
end
puts say_goodnight('Pa')
输出结果:
Good night, Pa
Ruby构建这个字符串对象时,它找到name的当前值并把它替换到字符串中。任意复杂的表达式都允许放在#{…}结构中。这里调用在所有字符串中定义的capitalize方法,将参数的首字母改为大写之后输出。
defsay_goodnight(name)
result = "Good night, #{name.capitalize}"
return result
end
puts say_goodnight('uncle')
输出结果:
Good night, Uncle
为了方便起见,如果表达式只是一个全局实例或类变量(马上会讲到),则不需要提供花括号。
$greeting               = "Hello"   #$greeting 是全局变量
@name    ="Prudence"   #@name 是实例变量
puts "
#$greeting,#
$name"
输出结果:
Hello, Prudence
有关字符串更多的信息和其他Ruby标准类型,请阅读从第59页开始的第5章。
最后,可以进一步地简化这个方法。Ruby方法所返回的值,是最后一个被求值的表达式的值,所以可以把这个临时变量和return语句都去掉。
defsay_goodnight(name)
"Good night, #{name}"
end
puts say_goodnight('Ma')
输出结果:
Good night, Ma
我们保证本节要简略,最后再讲一个内容:Ruby的名称。为简短起见,我们会使用一些没有在这里定义的术语(比如类变量,class variable)。当然,我们现在就会开始使用这些术语,这样在后面谈到类变量等东西的时候,你就不会感到陌生了。
Ruby使用一种命名惯例来区分名称的用途:名称的第一个字符显示这个名称如何被使用。局部变量、方法参数和方法名称都必须以小写字母或下画线开始。全局变量都有美元符号($)为前缀,而实例变量以“at”(@)符号开始。类变量以两个“at”(@@)符号开始。最后,类名称、模块名称和常量都必须以一个大写字母开始。各种名称的示例在图2.1中给出。
从上述规定的初始字符之后开始,名称可以是字母、数字和下画线的任意组合(但跟在@符号之后的符号不能是数字)。但是按惯例,包含多个单词的实例变量名称在词与词之间使用下画线连接,包含多个单词的类变量名称使用混合大小写(每个单词首字母大写)。方法名称可以?、!和=字符结束。

2010年01月04日 11点01分 12
level 6
mew~mew~ 楼主
2.3 数组和散列表
Arrays and Hashes
Ruby的数组(arrays)和散列表(hashes)是被索引的收集(indexedcollections)。两者都存储对象的集合,通过键(key)来访问。数组的键是整数,而散列表支持以任何对象作为它的键。数组和散列表会按需调整大小来保存新的元素。访问数组元素是高效的,但是散列表提供了灵活性。任何具体的数组或散列表可以保存不同类型的对象;马上就会看到,一个数组可以包含一个整数、一个字符串和一个浮点数。
使用数组字面量(array literal)——即方括号之间放一组元素——可以创建和初始化新的数组对象。有了数组对象,在方括号之间提供索引便可以访问单个元素,如下例所示。注意Ruby数组的索引从零开始。
a = [ 1, 'cat', 3.14 ]    # 有三个元素的数组
# 访问第一个元素
a[0] ® 1
# 设置第三个元素
a[2] = nil
# 显示这个数组
a    ® [1, "cat", nil]
你可能已注意到在这个例子中使用了nil这个特别的值。许多语言中nil(或null)的概念是指“没有对象”。在Ruby中,这是不一样的;nil是一个对象,与别的对象一样,只不过它是用来表示没有任何东西的对象。让我们继续介绍数组和散列表。
表2.1 变量和类名称样例
局部变量
   全局变量
   实例变量
   类变量
   常量和类名称
  
name
fish_and_chips
x_axis
thx1138
_26
   $debug
$CUSTOMER
$_
$plan9
$Global
   @name
@point_1
@X
@_
@plan9
   @@total
@@symtab
@@N
@@x_pos
@@SINGLE
   PI
FeetPerMile
String
MyClass
JazzSong
  
有时候创建一组单词的数组是一件痛苦的事情——要处理许多引号和逗号。幸运的是,Ruby有一种快捷方式:%w能够完成我们想做的事情。
a = [ 'ant', 'bee', 'cat', 'dog', 'elk' ]
a[0] ® "ant"
a[3] ® "dog"
# this is the same:
a = %w{ant bee catdog elk}
a[0] ® "ant"
a[3] ® "dog"
Ruby的散列表与数组相似。散列表字面量(hash literal)使用花括号而不是方括号。这个字面量必须为每一项提供两个对象:一个键(key)和一个值(value)。
例如,你可能想将乐器映射到它们所属的交响乐章节,可以用散列表这么来做:
inst_section = {
   'cello'    =>'string',
'clarinet' =>'woodwind',
'drum'     =>'percussion',
'oboe'      => 'woodwind',
'trumpet' =>'brass',
'violin' => 'string'
}
=>的左边是键(key),右边是其对应的值(value)。在一个散列表里面,键必须是唯一的(不能有两个“drum”项)。散列表里面的键和值可以是任意对象——你可能会有这样的散列表,它的值是数组或别的散列表等。
散列表使用与数组相同的方括号表示法来进行索引。
inst_section['oboe'] ® "woodwind"
inst_section['cello'] ® "string"
inst_section['bassoon']      ® nil
正如上例所示,默认情况下,如果用一个散列表没有包含的键进行索引,散列表就返回nil。通常这样是很方便的,比如在条件表达式中nil就意味着false。而有时候你可能想改变这个默认动作。比如使用散列表来计算每个键出现的次数时,如果这个默认值是0的话就会很方便。这很容易做到:当创建一个新的空散列表时,可以指定一个默认值。
histogram =Hash.new(0)
histogram['key1']   ® 0
histogram['key1'] = histogram['key1'] + 1
histogram['key1']   ® 1
数组和散列表对象包含大量有用的方法:详见第43页开始的讨论,以及第427页和第492页开始的参考手册。

2010年01月04日 11点01分 13
level 6
mew~mew~ 楼主
2.4 控制结构
Control Structures
Ruby具有所有常见的控制结构,如if语句和while循环。Java、C和Perl程序员可能会对这些语句的程序体“缺乏花括号”不太适应。Ruby是使用end关键字来表明程序体的结束。
if count > 10
puts "Try again"
elsif tries == 3
puts "You lose"
else
pus "Enter a number"
end
同样地,while语句以end结束。
while weight <100 and num_pallets <= 30
pallet =next_pallet()
weight +=pallet.weight
num_pallets+= 1
end
大多数Ruby语句会返回值,这意味着可以把它们当条件使用。例如,gets方法从标准输入流返回下一行,或者当到达文件结束时返回nil。因为Ruby在条件中把nil当作一个假值(false)对待,可以像下面这样来处理文件中的行。
while line = gets
putsline.downcase
end
在这里赋值语句要么把变量line设置为下一行,要么是nil,然后while语句测试这个赋值的结果,如果它是nil,则终止这个循环。
如果if或while语句的程序体只是一个表达式,Ruby的语句修饰符(statement modifiers)是一种有用的快捷方式。只要写出表达式,后面跟着if或while和条件。比如,这是if语句的例子。
if radiation >3000
puts "Danger, Will Robinson"
end
用语句修饰符重新编写了同样这个例子。
puts "Danger, While Robinson" if radiation > 3000
同样地,下面是while循环语句
square = 2
while square <1000
square =square*square
end
用语句修饰符,这个语句变得更简洁了。
square = 2
square =square*square while square < 1000
Perl程序员应该对这些语句修饰符很熟悉。

2010年01月04日 11点01分 14
level 6
mew~mew~ 楼主
2.6 Block和迭代器
Blocks and Iterators
本节会简单地描述Ruby的一个独特特性。Block,一种可以和方法调用相关联的代码块,几乎就像参数一样。这是一个不可思议的功能强大的特性。一位评论家曾经说:“这个特性是相当的有趣和重要,如果以前没有注意到,从现在开始你应当要注意了。”必须同意他的观点是
正确的

可以用block实现回调(但它比Java的匿名内部(anonymous inner)类更简单),传递一组代码(但它远比C的函数指针灵活),以及实现迭代器。
Block只是在花括号或者do…end之间的一组代码。
{ puts "Hello" }     # this is ablock
do                       ###
club.enroll(person)      # and so is this
person.socialize        #
end                      ###
为什么有两种分界符?部分原因是有人觉得有时候用一种分界符比另外一种感觉更自然。另外一部分原因是它们有不同的优先级:花括号比do/end绑定的更紧密些。在本书中,我们尝试遵循正在成为Ruby标准的一个约定俗成,单行block用花括号,多行block用do/end。
一旦创建了block,就可以与方法的调用相关联。把block的开始放在含有方法调用的源码行的结尾处,就可以实现关联。比如,在下面的代码中,含有puts "Hi"的block与greet方法的调用相关联。
greet { puts "Hi" }
如果方法有参数,它们出现在block之前。
verbose_greet("Dave", "loyalcustomer") { puts "Hi" }
然后使用Ruby的yield语句,方法可以一次或多次地调用(invoke)相关联的block。可以把yield想象成比如方法调用,它调用含有yield语句的方法所关联的block。
下面的例子显示了如何使用yield语句。定义了一个方法,它会调用yield两次。然后调用这个方法,把block放在同一行,在方法调用之后(并在方法的所有参数之后)。
def call_block
puts"Start of method"
yield
yield
puts"End of method"
end
call_block { puts"In the block" }
输出结果:
Start of method
In the block
In the block
End of method
看看(puts"In the block")block中的代码如何被执行两次,每次调用yield时,代码都会被执行。
可以提供参数给对yield的调用:参数会传递到block中。在block中,竖线(|)之间给出参数名来接受这些来自yield的参数。
def call_block
yield("hello",99)
end
call_block {|str,num| ... }
在Ruby库中大量使用了block来实现迭代器:迭代器是从某种收集(collection)如数组中连续返回元素的方法。
animals = %w( antbee cat dog elk ) # 创建一个数组
animals.each{|animal| puts animal } # 迭代它的内容
输出结果:
ant
bee
cat
dog
elk
让我们看一下如何实现应用在前面例子中的Array类中的each迭代器。each迭代器循环处理数组中的元素,对每个元素调用yield。在伪码中,它可能写成:
# 在Array类中……
def each
for eachelement    # <-- 无效的Ruby语句
    yield(element)
end
end
许多内建于C和Java等语言的循环结构在Ruby中只是方法调用,这些方法会零次或多次地调用相关联的block。
[ 'cat', 'dog','horse' ].each {|name| print name, " " }
5.times { print"*" }
3.upto(6) {|i| printi }
('a'..'e').each{|char| print char }
输出结果:
cat dog horse*****3456abcde
上面的代码要求对象5五次调用block;然后要求对象3调用一个block,并传入一个连续的值,直到这个值到达6为止。最后对a到e的字符区间(range),使用each方法调用block。

2010年01月04日 11点01分 16
level 6
mew~mew~ 楼主
2.8 更高更远
Onward and Upward
好了,我们已经介绍完了Ruby的一些基本特性。已经知道了对象、方法、字符串、容器和正则表达式,看到了一些简单的控制结构和一些小巧的迭代器。本章的内容构成了学习本书其他部分的基本知识。
随着时间的推移,该是前进到更高层次的时候了。下面我们会介绍类和对象。这两个概念是Ruby最高级别的构成部分,同时也是整个语言的支柱。

2010年01月04日 11点01分 18
level 6
mew~mew~ 楼主
单元测试(在下一页的侧栏描述)是一项帮助开发者编写更好的代码的技术。测试在代码被实际编写之前就有帮助,因为它自会引导你创建更好、更解耦的设计。当你编写代码时,它也很有帮助,可以向你及时反馈代码是否准确无误。在你完成代码之后,测试同样很有帮助,因为它能够让你检查代码是否可以正常工作,并且还帮助其他人理解如何使用你的代码。
单元测试是好事情。
但是,在一本Ruby的书籍中,为什么将关于单元测试的一章放在中间部分呢?因为单元测试对类如Ruby这样的动态语言,似乎携手而来。Ruby的灵活性使得编写测试非常简单,而测试让你能够更容易地验证你的代码。一旦渐入佳境,你会发现自己通常以这样一种步骤工作:先编写一些代码,再编写一两个测试,验证所有事情都完美正确,然后编写更多的代码。
单元测试还很琐碎——运行一个程序来调用应用的部分代码,得到若干结果,然后检查结果是否如你所愿。
假定我们测试一个罗马数字类。目前代码还很简单:它只是让我们创建一个表示特定数字的对象,并将该对象以罗马数字显示。第153页中的插图12.1,演示了在我们实现中的第一个刺痛。
我们通过编写另一个程序来测试这段代码,如下所示。
require 'roman'
r = Roman.new(1)
fail "'i'expected" unless r.to_s == "i"
r = Roman.new(9)
fail "'ix'expected" unless r.to_s == "ix"
不过,随着项目中测试数量的增长,这种自由散漫(ad-hoc)的方式会使测试变得复杂而难于管理。多年以来,出现了许多单元测试框架来帮助组织测试过程。Ruby自带了一个预安装的、由NathnielTalbott编写的Test::Unit框架。
何为单元测试
What is UnitTesting
单元测试专注于小块(单元)的代码,一般是单个方法或方法中的几行。这和其他的将整个系统视为一体的测试形式区别开来。
为何要专注于斯呢?因为最后所有软件都是以层次而架构的:某一层次的代码依赖于下一层代码的正确操作。如果下层的代码结果包含了bug,那么所有上面的层次都会有潜在的影响。这是一个大问题。Fred可能每星期编写的代码包含一个bug,而两个月以后,你可能会间接地调用了它。当你的代码产生不正确的结果时,你可能颇费一段时间来追溯,问题产生在Fred的方法中。当你询问Fred为什么这样编写这个方法时,回答很可能是“我不记得了,都过去两个月了。”
如果Fred对它的代码进行了单元测试,可能发生两种情况。第一,当代码在头脑中依然鲜活时,他就找出了bug。第二,因为单元测试只查看他刚刚编写的代码,当bug真的发生时,他只需要查看少数代码行就可以发现,而不必在整个代码基(code base)上考古了。
12.1 Test::Unit框架
Test::Unit Framework
Test::Unit框架基本上是将3个功能包装到一个整洁的包中。
l          它提供了一种表示单个测试的方式。
l          它提供了一个框架来组织测试。
l          它提供了灵活的方式来调用测试。
12.1.1 断言==预期的结果
Assertions ==Expected Results
与其让你在测试中编写一系列单独的if语句,还不如让Test::Unit提供一系列断言(assertion)来达到同样的目标。虽然存在许多不同风格的断言,但是它们基本上都遵循相同的模式。每个断言都向你提供指定预想的结果或输出、以及传入实际输出的方式。如果实际结果和预期的不同,断言会输出一条漂亮的消息,并将此次记录为一次失败。
例如,我们可以使用Test::Unit重写之前对于Roman类的测试。现在,忽略开始和结束处的框架(scaffolding)代码,只考察assert_equal方法。
图12.1 产生罗马数字(有bug
2010年01月04日 11点01分 19
level 6
mew~mew~ 楼主
12.2 组织测试
Structuring Tests
之前我们让你忽略测试外围的框架代码,现在是时候来考察它们了。
在你的单元测试中,使用下面的代码行包含Test::Unit的功能。
require 'test/unit'
单元测试,可以很自然地被组织成更高层的形式,叫做测试用例(test case);或者分解成较底层的形式,也就是测试方法本身。测试用例通常包括和某个特定功能或特性相关的所有测试。我们的Roman数字类非常简单,因此所有测试只需放在一个单独的测试用例中。在这个测试用例中,我们可能希望将你的断言组织成一系列的测试方法,每个方法都包括针对某种测试类型的断言:例如一个方法可能检查一般的数字转换,而另一个可以测试错误处理,等等。
表示测试的类必须是Test::Unit::TestCase的子类。含有断言的方法名必须以test开头。这是很重要的:Test::Unit使用反射(reflection)来查找要运行的测试,而只有以test开头的方法才符合条件。
你经常可以发现,测试用例中的所有测试方法都会设置一个特定的场景(scenario)。每个测试方法考察该场景的某些方面。最终,每个方法会总结得到自己的结果。例如,我们要测试一个从数据库提取点唱机播放列表的类。
require 'test/unit'
require'playlist_builder'
require 'dbi'
classTestPlaylistBuilder < Test::Unit::TestCase
deftest_empty_playlist
    db = DBI.connect('DBI:mysql:playlists')
    pb = PlaylistBuilder.new(db)
    assert_equal([], pb.playlist())
    db.disconnect
end
deftest_artist_playlist
    db = DBI.connect('DBI:mysql:playlists')
    pb = PlaylistBuilder.new(db)
    pb.include_artist("krauss")
    assert(pb.playlist.size > 0, "Playlist shouldn't be empty")
    pb.playlist.each do |entry|
      assert_match(/krauss/i, entry.artist)
    end
    db.disconnect
end
deftest_title_playlist
    db = DBI.connect('DBI:mysql:playlists')
    pb = PlaylistBuilder.new(db)
    pb.include_title("midnight")
    assert(pb.playlist.size > 0, "Playlist shouldn't be empty")
    pb.playlist.each do |entry|
      assert_match(/midnight/i, entry.title)
    end
    db.disconnect
end
# ...
end
输出结果:
Loaded suite -
Started
...
Finished in 0.004809seconds.
3 tests, 23assertions, 0 failures, 0 errors
每个测试首先连接数据库,然后创建一个新的播放列表生成器。最后每个测试和数据库断开。(在单元测试中使用真实数据库的想法是很成问题的,因为单元测试假定是
快速运行、上下文无关并且易于设定的,但是它说明了这一点。)
我们可以把这些通用的代码提取到setup和teardown方法中。在一个TestCase类中,一个叫做setup的方法将在每个测试方法之前运行,而叫做teardown的方法在每个测试方法结束之后运行。让我们强调一下:setup和teardown方法是对每个测试、而非每个测试用例运行一次。
我们的测试可以变为:
require 'test/unit'
require'playlist_builder'
require 'dbi'
classTestPlaylistBuilder < Test::Unit::TestCase
def setup
    @db = DBI.connect('DBI:mysql:playlists')
    @pb = PlaylistBuilder.new(@db)
end
def teardown
    @db.disconnect
end
deftest_empty_playlist
    assert_equal([], @pb.playlist())
end
deftest_artist_playlist
    @pb.include_artist("krauss")
    assert(@pb.playlist.size > 0, "Playlist shouldn't be empty")
    @pb.playlist.each do |entry|
      assert_match(/krauss/i, entry.artist)
    end
end
deftest_title_playlist
    @pb.include_title("midnight")
    assert(@pb.playlist.size > 0, "Playlist shouldn't be empty")
    @pb.playlist.each do |entry|
      assert_match(/midnight/i, entry.title)
    end
end
# ...
end
输出结果:
Loaded suite -
Started
...
Finished in 0.00691seconds.
3 tests, 23assertions, 0 failures, 0 errors

2010年01月04日 11点01分 21
level 6
mew~mew~ 楼主
12.3 组织和运行测试
Organizing and Running Tests
我们目前所演示的测试用例都是可以运行的Test::Unit程序。例如,如果Roman类的测试用例位于文件test_roman.rb中,我们可以使用下面的形式从命令行运行测试:
% ruby test_roman.rb
Loaded suite test_roman
Started
..
Finished in 0.039257seconds.
2 tests, 9assertions, 0 failures, 0 errors
Test::Unit足够聪明,可以发现没有主程序,因此它会将所有的测试用例类集合起来并依次运行。
如果我们希望如此,可以让它运行一个特定的测试方法。
% rubytest_roman.rb --name test_range
Loaded suitetest_roman
Started
.
Finished in 0.006445seconds.
1 tests, 4assertions, 0 failures, 0 errors
12.3.1 何处存放测试
Where to Put Tests
一旦你习惯了单元测试,你可能会发现自己产生的测试代码几乎和产品代码一样多。所有这些测试都必须放在某个地方。问题是,如果你将它们和一般的产品代码源文件放在一起,你的目录会变得很臃肿——因为实际上你最终为每个产品源文件配备了两个文件。
一般的解决方法是建立一个test/目录,将你所有的测试源文件放置其中。这个目录和保存开发代码的目录平行放置。例如,对Roman数字类,我们具有下面的目录结构:
这是一个可用的组织文件的方式,但是留给你一个小问题:如何告诉Ruby到哪里查找要测试的库文件?例如,如果我们的TestRoman测试代码在test/子目录中,Ruby如何知道从哪里查找我们要测试的roman.rb源文件呢?
一种不太可靠的方法是,将路径放到测试文件的require语句中,然后从test/子目录中运行测试。
require 'test/unit'
require'../lib/roman'
class TestRoman <Test::Unit::TestCase
# ...
end
为什么这种方法无法奏效呢?第一,因为我们的roman.rb文件本身也可能需要类库中编写的其他文件。它将使用require(而没有“..lib/”前缀)将它们加载进来,因为它们并不位于Ruby的$LOAD_PATH中,因此无法找到。我们的测试因而也无法运行。第二,一个不那么直接的问题是,我们将无法使用相同的测试来测试安装到目标系统上的类,所以我们应该简单地使用require'roman'来引用它们。
一种稍好些的解决方法是,从被测试库的父目录中运行测试。因为当前的目录是在加载路径中,测试代码将可以找到它。
% ruby../test/test_roman.rb
不过,当你想要运行系统中其他地方的测试时,这种方法就无法奏效了。可能,你所规划的构建过程要查找并执行名为test_xxx的文件来为应用中的所有软件运行测试。在这种情况下,你需要一点加载路径的魔法。在你测试代码的头部(例如在test_roman.rb中),添加下面的代码行:
$:.unshiftFile.join(File.dirname(__FILE__), "..", "lib")
require ...
这种技法可以工作,是因为测试代码相对被测试代码的存放位置是已知的。它首先得出测试文件运行所在的目录,然后构造被测试文件的路径。这个目录被加入加载路径(变量$:)的前部。之后,类似如require 'roman'的代码将首先搜索到被测试的库。
12.3.2 测试套件
Test Suites
一段时间之后,你的应用的测试用例集合可能会有可观的增长。你可能会发现类聚的倾向:一组用例测试某个特定的功能集合,而另一组用例测试另一个功能集合。这样的话,你可以将这些测试用例一并组成测试套件(test suite),然后以组的形式运行它们。
这在Test::Unit中是很容易的。你所要做的,就是创建一个要求加载test/unit的Ruby文件,然后要求加载每个你希望成组的、包含测试用例的文件。在这种方式下,你为自己建立了测试资料的一种层次结构。

2010年01月04日 11点01分 22
level 6
mew~mew~ 楼主
l          你可以通过名字来运行单个的测试。
l          你可以通过某个文件来运行其中的所有测试。
l          你可以将多个文件组成一个测试套件,作为运行测试的单元。
l          你可以将多个测试套件组成另外的测试套件。
这让你可以在所掌控的任何粒度级别运行单元测试,只测试一个方法,或者测试整个应用。
至此,值得我们考虑一下命名约定。Nathaniel Talbott,Test::Unit的作者,使用这样的约定,测试用例在以tc_xxx命名的文件中,而测试套件在ts_xxx命名的文件中。
# file ts_dbaccess.rb
require 'test/unit'
require 'tc_connect'
require 'tc_query'
require 'tc_update'
require 'tc_delete'
现在,如果你运行ts_dbaccess.rb,将会执行所要求加载的4个文件中的全部测试用例。
这就是全部了么?还不是,如果你希望,你可以使它更复杂。你可以手工地创建并填充TestSuite对象,但是在实践中好像没什么意义。如果你想要查找更多信息,ri Test::Unit应该可以帮助你。
Test::Unit还带有许多时髦的GUI测试运行器。不过,因为真正的程序员使用命令行,这里就不介绍它们了。再次,关于细节请参见文档。
assert(boolean, [ message ])
    如果boolean为false或nil,则失败。
assert_nil(obj, [ message ])
assert_not_nil(obj, [ message] )
    预期obj为(或不为)nil。
assert_equal(expected, actual,[ message ] )
assert_not_equal(expected, actual,[ message ] )
    使用==来判定obj和expected是否相等。
assert_in_delta(expected_float, actual_float, delta, [ message ] )
    预期实际的浮点值处于预期值的delta(偏差)之内。
assert_raise(Exception, . . . ) { block}
assert_nothing_raised(Exception, .. . ) { block }
    预期block将会(或不会)引发参数列出的一个异常。
assert_instance_of(klass, obj,[ message ] )
assert_kind_of(klass, obj, [message ] )
    预期obj是klass的子类或实例。
assert_respond_to(obj, message,[ message ] )
    预期obj能够响应message(一个符号)。
assert_match(regexp, string,[ message ] )
assert_no_match(regexp, string,[ message ] )
    预期string符合(或不符合)正则表达式regexp。
assert_same(expected, actual,[ message ] )
assert_not_same(expected, actual,[ message ] )
    预期expected.equal?(actual)。
assert_operator(obj1, operator,obj2, [ message ] )
    预期用obj2为参数调用obj1的operator,且结果为true。
assert_throws(expected_symbol, [ message] ) { block }
    预期block抛出指定的符号。
assert_send(send_array, [ message] )
    将send_array[1]中的消息发送给send_array[0]中的接收者,将send_array的其余部分作为参数。预期返回值为true。
flunk(message="Flunked")
    永远失败。
图12.2 Test::Unit支持的断言

2010年01月04日 11点01分 23
level 6
mew~mew~ 楼主
你应该已经注意到在Ruby中我们没有声明变量和方法的类型——一切都是某种对象。
现在,似乎人们对此有两种反应。一些人喜欢这种灵活性,对用动态类型的变量和方法编写代码感觉得心应手。如果你是这样的人,你可能希望跳到下页的“类不是类型 (Classes aren’tTypes)”章节。然而,另一些人对不受限制地使用所有这些变量会感到紧张不安。如果你是从C#或Java等语言转向Ruby的,你会觉得Ruby太不严谨了,以至于无法用它来编写“真正”的应用,在那些语言中,你已经习惯了为所有的变量和方法都指定一个类型。
不!
我们会用几个段落的篇幅来试图说服你,缺乏静态类型对编写可靠的应用来说并不是一个问题。在这里我们不是试图谴责别的语言。相反,我们只是对比方法上的不同。
现实情况是,在大多数主流语言中,静态类型系统在程序安全方面没有真正地起到太大作用。如果Java的类型系统可靠,举例来说,Java就无须实现ClassCastException异常。但是这个异常是需要的,因为Java中存在运行时的类型非确定性(与C++、C#和别的语言一样)。静态类型(typing)有助于代码优化,并通过工具提示(tooltip)帮助那些集成开发环境(IDE)做得更聪明些,但是我们还没有看到很多证据表明它促进了编写更为安全可靠的代码。
另一方面,一旦用了Ruby一段时间,你意识到动态类型的变量实际上在很多方面提高了你的生产率。你也会惊讶地发现,对类型混乱感到惶恐其实是毫无根据的。大型的、长时间运行的Ruby程序执行一些关键应用,却并不抛出任何类型相关的错误。为什么?
部分原因是:这是一个常识问题。如果用Java编程(Java1.5以前版本),所有容器实际上都是无类型的:容器里的任何东西都是Object,当抽取出元素时需要把它转换成所需的类型。当运行这些程序时,可能永远无法看到ClassCastException异常。你的代码结构不允许:你放入Person对象,然后取出Person对象。你不会编写以其他方式工作的程序。
好吧,在Ruby中也如此。如果你出于某种目的使用了一个变量,当你接下来再次使用它时,几乎总可能是因为相同的原因而使用。所谓可能会发生的混乱根本就不会发生。
在这之上,有丰富Ruby编程经验的人往往倾向于采用某种编程风格。他们编写了很多简短的方法,并一边编写一边测试。简短的方法意味着大多数变量的作用范围是受限制的:这样,程序因类型而出错的情况就少些。同时测试会帮助尽早发现那些愚蠢的错误:输入错误(typo)以及类型错误,使它们没有机会在代码中扩散开来。
结论是:在“类型安全”中的“安全”常常是一种错觉,用动态语言如Ruby编程是安全的,同时有很高的生产率。因此,如果你为Ruby缺乏静态类型感到紧张不安,我们建议你暂时把担心放在一旁,并试着用Ruby一段时间。一旦开始挖掘出使用动态类型的能力,那么你会惊讶地看到,你很少会见到因为类型问题而带来的错误,并会惊讶地发现你的生产率是如此之高。
23.1 类不是类型
Classes Aren’t Types
类型的问题,实际上比强类型拥护者和支持动态类型的叛逆青年之间正在进行的争吵,要复杂得多。真正的问题是,类型究竟是什么?
如果有使用传统类型语言编程的经验,你可能被这样教育过,对象的类型(type)是它的类(class)——所有对象都是某个类的实例,类(class)是对象的类型(type)。类定义了对象能够支持的操作(方法),同时定义了这些方法所操作的状态(实例变量)。看看Java代码。
Customer c;
c = database.findCustomer("dave");    /* Java */
这段代码声明了变量c是Customer类型,把它设置为对Dave客户对象的引用,这个对象是从某些数据库记录中创建的。所以c里面的对象类型是Customer,对吗?
可能是。但是,即使在Java中这个问题也比我们想象得要复杂。Java支持interface概念,interface是某种弱化的(emasculated)抽象基类。一个Java类可以声明其实现了某个接口。使用这个功能,你可以用如下方式定义你的类。

2010年01月04日 11点01分 24
level 0
    
s
2010年01月19日 07点01分 32
level 1
2013年09月15日 03点09分 33
level 9
这书好老 , ruby1.8.2 版本。 语法好像没变。
2013年09月15日 12点09分 34
1