`

【转】ruby 的那些绕不过的坑

    博客分类:
  • ruby
阅读更多

转自: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()(带括号)在调用父类方法时不带任何参数,正如我们期待的那样。

延伸阅读:Super keyword in Ruby

自定义异常不能继承 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 Ruby Ruby Ruby Ruby Ruby

    Ruby-使用Ruby绕过CloudFlare

    使用Ruby绕过CloudFlare,在Ruby中构建HatCloud。 它绕过CloudFlare来发现真正的IP。 如果您需要测试您的服务器和网站,这将非常有用。

    ruby DBI ruby DBI ruby DBI

    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互转,二进制字符串与整数互转的工具函数

    本资源是ruby代码,提供了一系列封装好的函数,用于快速进行转换,一个函数搞定,包括如下转换,二进制字符串与hex字符串的互转。二进制字符串与整数互转,包括uint8,uin16,uint32, 以及本地字节序和网络字节序两种...

    ruby2ruby.zip

    ruby2ruby 提供一些用来根据 RubyParser 兼容的 Sexps 轻松生成纯 Ruby 代码的方法。可在 Ruby 中轻松实现动态语言处理。 标签:ruby2ruby

    Ruby-rubybuild编译和安装Ruby

    Ruby是一种动态、开源的编程语言,以其简洁、优雅的语法和强大的元编程能力著称。在Ruby开发中,为了管理不同版本的Ruby环境,我们常常会使用到`rbenv`和`ruby-build`这两个工具。本文将详细介绍如何使用`ruby-build...

    Ruby-rubyinstall安装RubyJRubyRubiniusMagLevorMRuby

    Ruby是一种强大的、面向对象的脚本语言,广泛用于...对于那些需要在不同Ruby实现间切换的开发者来说,这是一个不可或缺的工具。通过熟练掌握`ruby-install`的使用,可以更好地适应不断变化的开发需求,提高开发效率。

    From Java to Ruby

    《From Java to Ruby》这本书是Java开发者转向Ruby语言的一份宝贵资源。它引导读者了解从传统的Java编程环境过渡到Ruby的动态世界时所遇到的概念差异和技术挑战。Ruby是一种灵活、简洁且富有表现力的编程语言,它...

    Ruby转JavaScript的编译器Opal.zip

    Opal 是一个 Ruby 转 JavaScript 的编译器. Opal 将 Ruby 源码转成 JavaScript 源码,运行速度很快,包含一个编译器、核心库和运行时实现。 标签:Opal

    Ruby-TensorStream用Ruby重新实现TensorFlow

    **Ruby-TensorStream:用Ruby重现实现TensorFlow** Ruby-TensorStream是一个开源项目,旨在为Ruby开发者提供一个类似于Google TensorFlow的深度学习框架。它的核心目标是让Ruby程序员能够利用TensorFlow的强大功能...

    Ruby完全自学手册 下

    《Ruby完全自学手册》是一本完全覆盖Ruby和Ruby on Rails的完全自学手册。《Ruby完全自学手册》的特色是由浅入深、循序渐进,注重理论和实践的结合。虽然定位为入门手册,但是依然涉及许多高级技术和应用,覆盖到的...

    ruby源代码 ruby源代码 ruby源代码 ruby源代码2

    ruby源代码 ruby源代码 ruby源代码 ruby源代码2

    src-oepkgs/ruby-ruby2ruby

    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的理解。描述中提到的“博文链接”指向了一个特定的博客文章,虽然没有给出...

    Ruby完全自学手册

    Ruby是一种简洁而功能强大的编程语言,由日本的松本行弘(Yukihiro "Matz" Matsumoto)在1993年开发,并于1995年公开发布。Ruby语言设计之初就非常注重开发人员的编程体验,它拥有自然、表达性强的语法,易于阅读和...

    Ruby-RubyCompiler针对Ruby设计的AOTAOT编译器

    虽然这可能会牺牲Ruby的一些灵活性(如运行时动态特性),但对于那些性能要求高的应用来说,这是一个值得考虑的解决方案。通过深入理解和使用这样的工具,开发者可以在保持Ruby语言的开发便利性的同时,提高应用的...

    ruby安装包下载 | ruby环境搭建

    Ruby是一种强大的动态编程语言,广泛应用于Web开发,脚本编写,服务器管理等领域。为了在没有外网连接的环境中搭建Ruby环境,你需要提前下载并准备相关的安装包。在提供的压缩包中,包含了三个关键文件:`ruby-2.7.2...

    ruby源代码 ruby源代码 ruby源代码 ruby源代码4

    ruby源代码 ruby源代码 ruby源代码 ruby源代码4

    Ruby Under a Microscope An Illustrated Guide to Ruby Internals

    总而言之,《Ruby Under a Microscope: An Illustrated Guide to Ruby Internals》这本书为Ruby爱好者和那些对编程语言实现感兴趣的读者提供了一个深入了解Ruby内部机制的窗口。通过阅读这本书,读者不仅可以欣赏到...

    crystal_ruby, 在Crystal中,编写 ruby 扩展.zip

    crystal_ruby, 在Crystal中,编写 ruby 扩展 crystal_ruby在晶体中写入 ruby 扩展。 这只是一个概念证明的证明。$ make test_ruby.bundlecrystal sample/test_ruby.cr --link-flags"-dynam

Global site tag (gtag.js) - Google Analytics