转自:https://ruby-china.org/topics/17742
首发:http://zhaowen.me/blog/2014/03/04/ruby-gotchas/
原文:Ruby Gotchas that will come back to haunt you
大多数 Ruby on Rails 的初学者们都会为这个出色的框架着迷,在缺乏 Ruby 语言知识的情况下就开始开发应用程序。这也无可厚非。至少,除非这些初学者们坚持了下来,然后摇身一变,成了没有 Ruby 基础知识的「senior」开发者。
不论如何,不管初学者还是有经验的程序员,迟早都会遇到传说中的「Ruby 的坑」。这些平时埋伏很深的语言上的细微之处将会耗费我们数个小时的死命调试( puts "1" ),查明真相后我们会惊呼「怎么会这样??!」,「好吧,我发誓这一次我会去看那本镐头封面的书!」,又或者,我们喊了声「操!」然后就去睡觉了。
我在这篇文章中列举了开发者们需要警惕的 Ruby 中常见的坑。我在每个条目中都给出了示例代码,包括了让人迷惑的或容易出错的代码。
另外,我也给出了最佳实践来简化你(和维护你代码的人)的生活。如果你对「最佳实践」不感冒,你也可以选择阅读详细的解释来了解为什么这个坑会引发 bug(多数情况下是因为它和你所想的不一样)。
and/or
不同于 &&/||
surprise = true and false # => surprise 的值为 true surprise = true && false # => surprise 的值为 false
最佳实践
只使用 && / ||
运算符。
详情
-
and / or
运算符的优先级比&& / ||
低 -
and / or
的优先级比=
低,而&& / ||
的优先级比=
高 -
and
和or
的优先级相同,而&&
的优先级比||
高
我们来给上述示例代码加上括号,这样就可以明显地看出 and
和 &&
在用法上的不同之处了。
(surprise = true) and false # => surprise is true surprise = (true && false) # => surprise is false
也有这样的说法:and / or
用于流程控制,而 && / ||
用于布尔型运算。但我认为:不要使用这些运算符的关键字版本(and / or / not
),而使用更为清晰的 if
和 unless
等。更加明确,更少困惑,更少 bugs。
延伸阅读:Difference between “or” and || in Ruby?
eql?
不同于 ==
(也不同于 equal?
或 ===
)
1 == 1.0 # => true 1.eql? 1.0 # => false
最佳实践
只使用 ==
运算符。
详情
==
、===
、eql?
和 equal?
都是互不相同的运算符,各自有不同的用法,分别用于不同的场合。当你要进行比较时,总是使用==
,除非你有特殊的需求(比如你 真的 需要区分 1.0
和 1
)或者出于某些原因重写(override)了某个运算符。
没错,eql?
可能看起来要比平凡的 ==
更为 聪明 ,但是你真的需要这样吗,去 区分两个相同的东西 ?
延伸阅读:What’s the difference between equal?, eql?, ===, and ==?
super
不同于 super()
class Foo def show puts 'Foo#show' end end class Bar < Foo def show(text) super puts text end end Bar.new.show('test') # ArgumentError: wrong number of arguments (1 for 0)
最佳实践
在这里,省略括号可不仅仅是品味(或约定)的问题,而是确实会影响代码的逻辑。
详情
- 使用
super
(没有括号)调用父类方法时,会将传给这个方法的参数原封不动地传给父类方法(因此在Bar#show
里面的super
会变成super('test')
,引发了错误,因为父类的方法不接收参数) -
super()
(带括号)在调用父类方法时不带任何参数,正如我们期待的那样。
自定义异常不能继承 Exception
class MyException < Exception end begin raise MyException rescue puts 'Caught it!' end # MyException: MyException # from (irb):17 # from /Users/karol/.rbenv/versions/2.1.0/bin/irb:11:in `<main>'
(上述代码不会捕捉到 MyException
,也不会显示 'Caught it!'
的消息。)
最佳实践
- 自定义异常类时,继承
StandardError
或任何其后代子类(越精确越好)。永远不要直接继承Exception
。 - 永远不要
rescue Exception
。如果你想要大范围捕捉异常,直接使用空的rescue
语句(或者使用rescue => e
来访问错误对象)。
详情
- 当你使用空的
rescue
语句时,它会捕捉所有继承自StandardError
的异常,而不是Exception
。 - 如果你使用了
rescue Exception
(当然你不应该这样),你会捕捉到你无法恢复的错误(比如内存溢出错误)。而且,你会捕捉到 SIGTERM 这样的系统信号,导致你无法使用 CTRL+C 来中止你的脚本。
延伸阅读:Why is it bad style to `rescue Exception => e` in Ruby?
class Foo::Bar
不同于 module Foo; class Bar
MY_SCOPE = 'Global' module Foo MY_SCOPE = 'Foo Module' class Bar def scope1 puts MY_SCOPE end end end class Foo::Bar def scope2 puts MY_SCOPE end end Foo::Bar.new.scope1 # => "Foo Module" Foo::Bar.new.scope2 # => "Global"
最佳实践
总是使用长的,更清晰的,module 把 class 包围的写法:
module Foo class Bar end end
详情
-
module
关键字(class
和def
也一样)会对其包围的区域创建新的词法作用域(lexical scope)。所以,上面的module Foo
创建了'Foo'
作用域,常量MY_SCOPE
和它的值'Foo Module'
就在其中。 - 在这个 module 中,我们声明了
class Bar
,又会创建新的词法作用域(名为'Foo::Bar'
),它能够访问父作用域('Foo'
)和定义在其中的所有常量。 - 然而,当你使用了这个
::
「捷径」来声明 Foo::Bar 时,class Foo::Bar
又创建了一个新的词法作用域,名字也叫'Foo::Bar'
,但它没有父作用域,因此不能访问'Foo'
里面的东西。 - 因此,在
class Foo::Bar
中我们只能访问定义在脚本的开头的MY_SCOPE
常量(不在任何 module 中),其值为'Global'
。
延伸阅读:Ruby – Lexical scope vs Inheritance
多数 bang!
方法如果什么都没做就会返回 nil
'foo'.upcase! # => "FOO" 'FOO'.upcase! # => nil
最佳实践
永远不要依赖于内建的 bang!
方法的返回值,比如在条件语句或流程控制中:
@name.upcase! and render :show
上面的代码会造成一些无法预测的行为(或者更准备地说,我们可以预测到当 @name
已经是全部大写的时候就会失败)。另外,这个示例也再一次说明了为什么你不应该使用 and/or
来控制流程。敲两个回车吧,不会有树被砍的。
@name.upcase! render :show
attribute=(value)
方法永远返回传给它的 value
而无视 return
值
class Foo def self.bar=(value) @foo = value return 'OK' end end Foo.bar = 3 # => 3
(注意这个赋值方法 bar=
返回了 3
,尽管我们显式地在最后 return 'OK'
。)
最佳实践
永远不要依赖赋值方法的返回值,比如下面的条件语句:
puts 'Assigned' if (Foo.bar = 3) == 'OK' # => nil
显然这个语句不会如你所想。
延伸阅读:ruby, define []= operator, why can’t control return value?
private
并不会让你的 self.method
成为私有方法
class Foo private def self.bar puts 'Not-so-private class method called' end end Foo.bar # => "Not-so-private class method called"
(注意,如果这个方法真的是私有方法,那么 Foo.bar
就会抛出 NoMethodError
。)
最佳实践
要让你的类方法变得私有,你需要使用 private_class_method :method_name
或者把你的私有类方法放到 class << self
block 中:
class Foo class << self private def bar puts 'Class method called' end end def self.baz puts 'Another class method called' end private_class_method :baz end Foo.bar # => NoMethodError: private method `bar' called for Foo:Class Foo.baz # => NoMethodError: private method `baz' called for Foo:Class
延伸阅读:creating private class method
我才不怕这些 Ruby 的坑!
上面列举的 Ruby 的坑可能看上去没什么大不了的,乍一看似乎只是属于代码风格和约定的范畴。
但相信我,如果你不重视它们,终有一天你会在 Ruby on Rails 的开发过程中碰到诡异无比的问题。它会让你心碎。因为你已经累觉不爱。然后孤独终老。永远。
相关推荐
Ruby Ruby Ruby Ruby Ruby Ruby
使用Ruby绕过CloudFlare,在Ruby中构建HatCloud。 它绕过CloudFlare来发现真正的IP。 如果您需要测试您的服务器和网站,这将非常有用。
ruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ruby DBIruby DBI ruby DBI ...
本资源是ruby代码,提供了一系列封装好的函数,用于快速进行转换,一个函数搞定,包括如下转换,二进制字符串与hex字符串的互转。二进制字符串与整数互转,包括uint8,uin16,uint32, 以及本地字节序和网络字节序两种...
ruby2ruby 提供一些用来根据 RubyParser 兼容的 Sexps 轻松生成纯 Ruby 代码的方法。可在 Ruby 中轻松实现动态语言处理。 标签:ruby2ruby
Ruby是一种动态、开源的编程语言,以其简洁、优雅的语法和强大的元编程能力著称。在Ruby开发中,为了管理不同版本的Ruby环境,我们常常会使用到`rbenv`和`ruby-build`这两个工具。本文将详细介绍如何使用`ruby-build...
Ruby是一种强大的、面向对象的脚本语言,广泛用于...对于那些需要在不同Ruby实现间切换的开发者来说,这是一个不可或缺的工具。通过熟练掌握`ruby-install`的使用,可以更好地适应不断变化的开发需求,提高开发效率。
《From Java to Ruby》这本书是Java开发者转向Ruby语言的一份宝贵资源。它引导读者了解从传统的Java编程环境过渡到Ruby的动态世界时所遇到的概念差异和技术挑战。Ruby是一种灵活、简洁且富有表现力的编程语言,它...
Opal 是一个 Ruby 转 JavaScript 的编译器. Opal 将 Ruby 源码转成 JavaScript 源码,运行速度很快,包含一个编译器、核心库和运行时实现。 标签:Opal
**Ruby-TensorStream:用Ruby重现实现TensorFlow** Ruby-TensorStream是一个开源项目,旨在为Ruby开发者提供一个类似于Google TensorFlow的深度学习框架。它的核心目标是让Ruby程序员能够利用TensorFlow的强大功能...
《Ruby完全自学手册》是一本完全覆盖Ruby和Ruby on Rails的完全自学手册。《Ruby完全自学手册》的特色是由浅入深、循序渐进,注重理论和实践的结合。虽然定位为入门手册,但是依然涉及许多高级技术和应用,覆盖到的...
ruby源代码 ruby源代码 ruby源代码 ruby源代码2
src-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2rubysrc-oepkgs/ruby-ruby2...
标题中的“ruby学习一个综合小练习”表明这是一个关于Ruby编程语言的学习资源,可能是通过一个实际的小项目或练习来帮助学习者提升对Ruby的理解。描述中提到的“博文链接”指向了一个特定的博客文章,虽然没有给出...
Ruby是一种简洁而功能强大的编程语言,由日本的松本行弘(Yukihiro "Matz" Matsumoto)在1993年开发,并于1995年公开发布。Ruby语言设计之初就非常注重开发人员的编程体验,它拥有自然、表达性强的语法,易于阅读和...
虽然这可能会牺牲Ruby的一些灵活性(如运行时动态特性),但对于那些性能要求高的应用来说,这是一个值得考虑的解决方案。通过深入理解和使用这样的工具,开发者可以在保持Ruby语言的开发便利性的同时,提高应用的...
Ruby是一种强大的动态编程语言,广泛应用于Web开发,脚本编写,服务器管理等领域。为了在没有外网连接的环境中搭建Ruby环境,你需要提前下载并准备相关的安装包。在提供的压缩包中,包含了三个关键文件:`ruby-2.7.2...
ruby源代码 ruby源代码 ruby源代码 ruby源代码4
总而言之,《Ruby Under a Microscope: An Illustrated Guide to Ruby Internals》这本书为Ruby爱好者和那些对编程语言实现感兴趣的读者提供了一个深入了解Ruby内部机制的窗口。通过阅读这本书,读者不仅可以欣赏到...
crystal_ruby, 在Crystal中,编写 ruby 扩展 crystal_ruby在晶体中写入 ruby 扩展。 这只是一个概念证明的证明。$ make test_ruby.bundlecrystal sample/test_ruby.cr --link-flags"-dynam