什么是Ruby Gotchas新手应该被警告?

Min*_*ark 106 ruby

我最近学习了Ruby编程语言,总而言之,它是一门很好的语言.但我很惊讶地发现它并不像我预期的那么简单.更准确地说,"最不惊讶的规则"对我来说似乎并不受到尊重(当然这是非常主观的).例如:

x = true and false
puts x  # displays true!
Run Code Online (Sandbox Code Playgroud)

和着名的:

puts "zero is true!" if 0  # zero is true!
Run Code Online (Sandbox Code Playgroud)

你会警告一个Ruby新手的其他"问题"是什么?

max*_*ori 57

Wikipedia Ruby陷阱

来自文章:

  • 以大写字母开头的名称被视为常量,因此局部变量应以小写字母开头.
  • 字符$@不指示变量的数据类型和Perl一样,而是充当范围分辨率运营商.
  • 要表示浮点数,必须使用零数字(99.0)或显式转换(99.to_f).附加点(99.)是不够的,因为数字易受方法语法的影响.
  • 非布尔数据的布尔评价是严格的:0,""并且[]都评价true.在C中,表达式0 ? 1 : 0求值为0(即假).然而,在Ruby中,它会产生1,因为所有数字都评估为true; 只nilfalse评价false.这个规则的必然结果是按惯例的Ruby方法 - 例如,正则表达式搜索 - 在成功时返回数字,字符串,列表或其他非假值,但是nil在失败时(例如,不匹配).此约定也用于Smalltalk,其中只有特殊对象true,false可以在布尔表达式中使用.
  • 1.9之前的版本缺少字符数据类型(与提供char字符类型的C相比).切片时可能会出现意外:"abc"[0]yield 97(一个整数,表示字符串中第一个字符的ASCII码); 获得"a"使用"abc"[0,1](长度为1的子串)或"abc"[0].chr.
  • statement until expression与其他语言的等效语句(例如do { statement } while (not(expression));在C/C++/...中)不同,符号实际上从不运行语句,如果表达式已经存在true.这是因为statement until expression实际上是语法糖

    until expression
      statement
    end
    
    Run Code Online (Sandbox Code Playgroud)

    ,等效其中在C/C++的是while (not(expression)) statement;就像statement if expression是一个相当于

    if expression
      statement
    end
    
    Run Code Online (Sandbox Code Playgroud)

    但是,符号

    begin
      statement
    end until expression
    
    Run Code Online (Sandbox Code Playgroud)

    事实上,即使表达式已经为真,Ruby也会运行一次语句.

  • 因为常量是对象的引用,所以更改常量引用的内容会生成警告,但修改对象本身则不会.例如,Greeting << " world!" if Greeting == "Hello"不会生成错误或警告.这与finalJava中的变量类似,但Ruby也具有"冻结"对象的功能,这与Java不同.

一些与其他语言明显不同的功能:

  • 条件表达式的常用运算符,and并且or不遵循正常的优先规则:and不会比更严格地绑定or.Ruby也有表达式运算符||,&&它可以按预期工作.

  • def内部def不会做Python程序员可能期望的:

    def a_method
        x = 7
        def print_x; puts x end
        print_x
    end
    
    Run Code Online (Sandbox Code Playgroud)

    这给出了关于x未定义的错误.你需要使用一个Proc.

语言功能

  • 如果方法采用多个参数,则省略方法参数周围的括号可能会导致意外结果.Ruby开发人员已经声明在未来的Ruby版本中可能不允许遗漏多参数方法的括号; 当前(2007年11月)Ruby解释器抛出一个警告,鼓励作者不要省略(),以避免代码含糊不清.不使用()仍然是常见的做法,并且使用Ruby作为人类可读的特定于域的编程语言本身以及所谓的方法可能特别好method_missing().


Min*_*ark 38

新手将遇到平等方法的麻烦:

  • a == b:检查a和b是否相等.这是最有用的.
  • a.eql?b :还检查a和b是否相等,但有时更严格(例如,它可能会检查a和b是否具有相同的类型).它主要用于哈希.
  • a.equal?b:检查a和b是否是同一个对象(身份检查).
  • a === b:用于case语句(我将其读作" a match b ").

这些例子应该澄清前3种方法:

a = b = "joe"

a==b       # true
a.eql? b   # true
a.equal? b # true (a.object_id == b.object_id)

a = "joe"
b = "joe"

a==b       # true
a.eql? b   # true
a.equal? b # false (a.object_id != b.object_id)

a = 1
b = 1.0

a==b       # true
a.eql? b   # false (a.class != b.class)
a.equal? b # false
Run Code Online (Sandbox Code Playgroud)

注意==,eql?平等?应始终是对称的:如果a == b则b == a.

还要注意==eql?是否在类Object中实现为等于别名,那么如果你创建一个新类并想要==eql?除了普通的身份之外,还有其他意义,那么你需要覆盖它们.例如:

class Person
    attr_reader name
    def == (rhs)
      rhs.name == self.name  # compare person by their name
    end
    def eql? (rhs)
      self == rhs
    end
    # never override the equal? method!
end
Run Code Online (Sandbox Code Playgroud)

===方法的行为不同.首先它是不是对称的(一=== B则不是意味着A在B ===一).正如我所说,你可以将=== b读作"匹配b".这里有一些例子:

# === is usually simply an alias for ==
"joe" === "joe"  # true
"joe" === "bob"  # false

# but ranges match any value they include
(1..10) === 5        # true
(1..10) === 19       # false
(1..10) === (1..10)  # false (the range does not include itself)

# arrays just match equal arrays, but they do not match included values!
[1,2,3] === [1,2,3] # true
[1,2,3] === 2       # false

# classes match their instances and instances of derived classes
String === "joe"   # true
String === 1.5     # false (1.5 is not a String)
String === String  # false (the String class is not itself a String)
Run Code Online (Sandbox Code Playgroud)

案件陈述是基于===方法:

case a
  when "joe": puts "1"
  when 1.0  : puts "2"
  when (1..10), (15..20): puts "3"
  else puts "4"
end
Run Code Online (Sandbox Code Playgroud)

相当于:

if "joe" === a
  puts "1"
elsif 1.0 === a
  puts "2"
elsif (1..10) === a || (15..20) === a
  puts "3"
else
  puts "4"
end
Run Code Online (Sandbox Code Playgroud)

如果你定义一个新类,其实例代表某种容器或范围(如果它有类似于include?匹配?方法),那么你可能会发现覆盖===方法是有用的,如下所示:

class Subnet
  [...]
  def include? (ip_address_or_subnet)
    [...]
  end
  def === (rhs)
    self.include? rhs
  end
end

case destination_ip
  when white_listed_subnet: puts "the ip belongs to the white-listed subnet"
  when black_listed_subnet: puts "the ip belongs to the black-listed subnet"
  [...]
end
Run Code Online (Sandbox Code Playgroud)


Dan*_*ton 20

  • 猴子补丁.Ruby有开放类,所以它们的行为可以在运行时动态改变...

  • 如果或已被覆盖,对象可能会响应未定义的方法.这利用了Ruby的基于消息的方法调用.RailsActiveRecord 系统使用它可以产生很好的效果.method_missingsend


Min*_*ark 18

以下代码让我感到惊讶.我认为这是一个危险的问题:既易于碰撞又难以调试.

(1..5).each do |number|
  comment = " is even" if number%2==0
  puts number.to_s + comment.to_s
end
Run Code Online (Sandbox Code Playgroud)

这打印:

1
2 is even
3
4 is even
5
Run Code Online (Sandbox Code Playgroud)

但是,如果我只是在阻止之前添加comment =任何东西 ......

comment = nil
(1..5).each do |number|
  comment = " is even" if number%2==0
  puts number.to_s + comment.to_s
end
Run Code Online (Sandbox Code Playgroud)

然后我得到:

1
2 is even
3 is even
4 is even
5 is even
Run Code Online (Sandbox Code Playgroud)

基本上,当一个变量仅在一个块内定义时,它会在块的末尾被销毁,然后nil在每次迭代时被重置.这通常是你所期望的.但是如果变量在块之前定义的,则外部变量在块内使用,因此其值在迭代之间是持久的.

一种解决方案是写这个:

comment = number%2==0 ? " is even" : nil
Run Code Online (Sandbox Code Playgroud)

我认为很多人(包括我)倾向于写" a = b if c而不是a = (c ? b : nil)",因为它更具可读性,但显然它有副作用.

  • 这对我来说似乎合乎逻辑.这个范围是其他语言的典型,它只是语法不同. (6认同)
  • 您也可以通过(1..5)do | number; comment |来遮蔽外部范围变量 .....请阅读http://stackoverflow.com/questions/1654637/ruby-forgets-local-variables-during-a-while-loop/1654665#1654665 (4认同)

Dan*_*aft 16

super没有参数的情况下调用时,实际上使用与重写方法相同的参数调用重写的方法.

class A
  def hello(name="Dan")
    puts "hello #{name}"
  end
end

class B < A
  def hello(name)
    super
  end
end

B.new.hello("Bob") #=> "hello Bob"
Run Code Online (Sandbox Code Playgroud)

要实际调用super没有参数,你需要说super().

  • 如果`B#hello`在`super`之前有'name = 42`,那么它就说"你好42". (3认同)

And*_*imm 14

块和方法默认返回最后一行的值.puts为了调试目的而添加语句可能会导致令人不快的副作用


Joh*_*ley 13

继承在决定 Ruby中的方法可见性方面不起作用.


Min*_*ark 11

我在理解类变量,类属性和类方法时遇到了很多麻烦.这段代码可能对新手有所帮助:

class A
  @@classvar = "A1"
  @classattr = "A2"
  def self.showvars
    puts "@@classvar => "+@@classvar
    puts "@classattr => "+@classattr
  end
end

A.showvars
  # displays:
  # @@classvar => A1
  # @classattr => A2

class B < A
  @@classvar = "B1"
  @classattr = "B2"
end

B.showvars
  # displays:
  # @@classvar => B1
  # @classattr => B2

A.showvars
  # displays:
  # @@classvar => B1   #Class variables are shared in a class hierarchy!
  # @classattr => A2   #Class attributes are not
Run Code Online (Sandbox Code Playgroud)


dyl*_*nfm 8

  • 块理解起来非常重要,它们随处可见.

  • 方法参数周围不需要括号.您是否使用它们取决于您自己.有人说你应该经常使用它们.

  • 使用raise和rescue进行异常处理,而不是抛出和捕获.

  • 您可以使用,;但除非您想在一行上放置多个内容,否则您不必使用.


小智 8

我学到的一件事是仔细使用运算符|| =.如果你正在处理布尔值,请特别小心.我通常使用|| = b作为一个捕获全部给'a'一个默认值,如果其他一切都失败了,'a'仍然是零.但如果a为假且b为真,则a将被指定为true.


Min*_*ark 7

我在使用包含实例方法类方法的mixin时遇到了麻烦.这段代码可能对新手有所帮助:

module Displayable
  # instance methods here
  def display
    puts name
    self.class.increment_displays
  end
  def self.included(base)
    # This module method will be called automatically
    # after this module is included in a class.
    # We want to add the class methods to the class.
    base.extend Displayable::ClassMethods
  end
  module ClassMethods
    # class methods here
    def number_of_displays
      @number_of_displays # this is a class attribute
    end
    def increment_displays
      @number_of_displays += 1
    end
    def init_displays
      @number_of_displays = 0
    end
    # this module method will be called automatically
    # after this module is extended by a class.
    # We want to perform some initialization on a
    # class attribute.
    def self.extended(base)
      base.init_displays
    end
  end
end

class Person
  include Displayable
  def name; @name; end
  def initialize(name); @name=name; end
end

puts Person.number_of_displays # => 0
john = Person.new "John"
john.display # => John
puts Person.number_of_displays # => 1
jack = Person.new "Jack"
jack.display # => Jack
puts Person.number_of_displays # => 2
Run Code Online (Sandbox Code Playgroud)

起初,我以为我可以通过简单地执行以下操作来使用包含实例方法类方法的模块:

module Displayable
  def display
    puts name
    self.class.increment_displays
  end
  def self.number_of_displays  # WRONG!
    @number_of_displays
  end
  [...]
end
Run Code Online (Sandbox Code Playgroud)

不幸的是,方法number_of_displays永远不会被包含或扩展,因为它是"模块类方法".只有"模块实例方法"可以包含在类中(作为实例方法)或扩展到类中(作为类方法).这就是为什么你需要将mixin的实例方法放入一个模块,并将mixin的类方法放入另一个模块中(通常将类方法放入"ClassMethods"子模块中).由于包含了魔术方法,您可以轻松地在一个简单的"包含可显示"调用中包含实例方法和类方法(如上例所示).

这个mixin将按每个类别计算每个显示.计数器是一个类属性,因此每个类都有自己的(如果你从Person类派生一个新类,你的程序可能会失败,因为派生类的@number_of_displays计数器永远不会被初始化).您可能希望用@@ number_of_displays替换@number_of_displays以使其成为全局计数器.在这种情况下,每个类层次结构都有自己的计数器.如果您想要一个全局且唯一的计数器,您应该将它作为模块属性.

当我开始使用Ruby时,所有这些对我来说绝对不是直观的.

我仍然无法弄清楚如何干净地使这些mixin方法中的一些私有或受保护(只有displaynumber_of_displays方法应作为公共方法包含在内).


pes*_*ous 7

注意范围表示法.

(至少,比最初做的更多关注!)

0..10 (两个点)和0...10(三个点)之间存在差异.

我非常喜欢Ruby.但这个点点对点点点的东西让我感到困惑.我认为这种微妙的双语法"功能"是:

  • 容易输入错误
  • 在浏览代码的同时,容易错过用眼睛

不应该在我的程序中造成毁灭性的错误.


Mik*_*use 6

我认为" and和" or是对Perl的点头,这是Ruby更明显的"父母"之一(其中最突出的是Smalltalk).它们都具有低得多的优先级(低于赋值,实际上,这是指出行为的来源)&&,||而不是你应该使用的运算符.

其他需要注意的事情并不是很明显:

你并没有真正调用方法/函数,虽然它看起来有点像.相反,如在Smalltalk中,您向对象发送消息.所以method_missing更像是message_not_understood.

some_object.do_something(args)
Run Code Online (Sandbox Code Playgroud)

相当于

some_object.send(:do_something, args) # note the :
Run Code Online (Sandbox Code Playgroud)

符号被广泛使用.这是那些开始的东西,:它们并不是立即显而易见的(好吧,它们不是我的意思)但是你越早掌握它们就越好.

Ruby在"鸭子打字"方面很重要,遵循"如果它像鸭子一样行走并且像鸭子一样呱呱......"这样的主体,它允许用一个共同的方法子集非正式地替换对象而没有任何明确的继承或混合关系.


Ale*_*fee 6

如果使用attr_writerattr_accessor(或def foo=)声明一个setter(也就是mutator),请注意从类中调用它.由于变量是隐式声明的,因此解释器总是必须解析foo = bar为声明一个名为foo的新变量,而不是调用该方法self.foo=(bar).

class Thing
  attr_accessor :foo
  def initialize
    @foo = 1      # this sets @foo to 1
    self.foo = 2  # this sets @foo to 2
    foo = 3       # this does *not* set @foo
  end
end

puts Thing.new.foo #=> 2
Run Code Online (Sandbox Code Playgroud)

这也适用于Rails ActiveRecord对象,它们根据数据库中的字段定义访问器.由于它们甚至不是@-style实例变量,因此单独设置这些值的正确方法是使用self.value = 123self['value'] = 123.


Zab*_*bba 6

方法可以重新定义,并且可能会变得令人费解,直到您找到原因为止。(诚​​然,当 Ruby on Rails 控制器的操作被错误地重新定义时,此错误可能有点“难以”检测!

#demo.rb
class Demo

  def hello1
    p "Hello from first definition"
  end

  # ...lots of code here...
  # and you forget that you have already defined hello1

  def hello1
    p "Hello from second definition"
  end

end
Demo.new.hello1
Run Code Online (Sandbox Code Playgroud)

跑步:

$ ruby demo.rb
=> "Hello from second definition"
Run Code Online (Sandbox Code Playgroud)

但是在启用警告的情况下调用它,您可以看到原因:

$ ruby -w demo.rb
demo.rb:10: warning: method redefined; discarding old hello1
=> "Hello from second definition"
Run Code Online (Sandbox Code Playgroud)


Chi*_*tan 5

了解Time和Date类之间的区别.两者都不同,并且在rails中使用它们时产生了问题.Time类有时会与标准ruby/rails库中的其他Time类库冲突.它个人花了我很多时间来了解我的rails应用程序中究竟发生了什么.后来,我想到了

Time.new

它指的是一个我甚至都不知道的位置的图书馆.

对不起,如果我不清楚我想说的话.如果其他人遇到类似问题,请重新解释.