应该警告新手的Ruby Gotchas是什么?[关闭]


108

我最近学习了Ruby编程语言,总而言之,它是一门好语言。但是令我惊讶的是,它并不像我期望的那么简单。更准确地说,“最小惊喜规则”在我看来并不十分受人尊敬(当然,这是非常主观的)。例如:

x = true and false
puts x  # displays true!

和著名的:

puts "zero is true!" if 0  # zero is true!

您会警告Ruby新手的其他“陷阱”是什么?


@ phrases.insert(0,p)好@ phrases.insert(p)什么都不会发生@phrases << p#OK
Anno2001

为什么true and false返回true?
尔根·保罗(

3
因为“ x = true和false”实际上被解释为“(x = true)和false”。这是运算符优先级的问题:“和”的优先级比“ =”低。其他大多数语言都具有相反的优先级,我不知道为什么他们在Rails中选择了此顺序,我感到非常混乱。如果要“正常”行为,只需键入“ x =(true and false)”,则x将为false。
MiniQuark

4
另一种解决方案是使用“ &&”和“ ||” 而不是“ and”和“ or”:它们的行为符合预期。例如:“ x = true && false”导致x为false。
MiniQuark

“最少惊讶的原则意味着最少惊讶的原则。” 来自en.wikipedia.org/wiki/Ruby_(programming_language)#Philosophy Python也是如此。我对Python的创建者有类似的引用,但我忘了它在哪里。
DarekNędza2014年

Answers:


59

维基百科Ruby陷阱

从文章:

  • 以大写字母开头的名称被视为常量,因此局部变量应以小写字母开头。
  • 字符$@并不表示Perl中的变量数据类型,而是用作范围解析运算符。
  • 要表示浮点数,必须在后面加上零数字(99.0)或显式转换(99.to_f)。附加点(99.)是不够的,因为数字容易受到方法语法的影响。
  • 非布尔数据的布尔值评估是严格的:0""并且[]都被评估为true。在C中,表达式的0 ? 1 : 0计算结果为0(即false)。但是,在Ruby中,1由于所有数字求和,因此它会产生true;只有nilfalse评估为false。该规则的一个推论是,按惯例,Ruby方法(例如,正则表达式搜索)在成功但nil失败(例如,不匹配)时返回数字,字符串,列表或其他非假值。此约定还用于Smalltalk,在Smalltalk中,只有特殊对象,true并且false可以在布尔表达式中使用。
  • 1.9之前的版本缺少字符数据类型(与C比较,后者提供char字符类型)。在切片字符串时,这可能会引起意外:"abc"[0]yields 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

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

    if expression
      statement
    end

    但是,表示法

    begin
      statement
    end until expression

    实际上,即使表达式已经为true,在Ruby中,语句也会运行一次。

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

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

  • 条件表达式and和的通常运算符or不遵循正常的优先级规则:and绑定不比严格or。Ruby也有表达运营商||&&其正常工作。

  • def内部def并没有Python程序员所期望的:

    def a_method
        x = 7
        def print_x; puts x end
        print_x
    end

    这给出了关于x未定义的错误。您需要使用Proc

语言特征

  • 如果方法采用多个参数,则省略方法参数周围的括号可能会导致意外结果。Ruby开发人员表示,在将来的Ruby版本中,可能不允许省略多参数方法的括号。当前(2007年11月),Ruby解释器抛出警告,鼓励写者不要忽略它(),以避免代码含义不明确。不使用()仍然是一种常见的做法,将Ruby与称为的方法一起用作人类可读的领域特定的编程语言本身尤其好method_missing()

1
Ruby 1.9也缺少字符数据类型。在1.8中,索引运算符返回一个Fixnum。在1.9中,它等效于切片一个字符的字符串。
whitequark 2011年

38

新手将无法使用平等方法

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

这些示例应阐明前三种方法:

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

注意==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

===方法的行为不同。首先它是不是对称的(一=== B则不是意味着A在B ===一)。如我所说,您可以将a === b读为“ a matchs 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)

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

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

等效于此:

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

如果定义一个新类,其实例表示某种容器或范围(如果它具有诸如include?match?方法之类的东西),那么您可能会发现覆盖这样的===方法很有用:

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

1
另外:a ='строка'; b ='строка'; pa == b; a = a.force_encoding'ASCII-8BIT'; b = b.force_encoding'UTF-8'; pa == b; pa === b; p a.eql?b; 等于 b
Nakilon


18

以下代码使我感到惊讶。我认为这是一个危险的陷阱:既容易遇到,又难以调试。

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

打印:

1
2 is even
3
4 is even
5

但是如果我在块之前添加comment =任何内容 ...

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

然后我得到:

1
2 is even
3 is even
4 is even
5 is even

基本上,当仅在块内定义变量时,然后在块末尾销毁该变量,然后nil在每次迭代时将其重置为。通常就是您所期望的。但是如果变量该块之前所定义,然后将外变量用于块内,并且它的值是因此迭代之间持久的。

一种解决方案是改为编写此代码:

comment = number%2==0 ? " is even" : nil

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


4
您也可以通过(1..5)对外部作用域变量进行阴影处理| number; comment | .....读到这里stackoverflow.com/questions/1654637/...
Özgür的

6
在我看来,这是合乎逻辑的。这种作用域是其他语言的典型代表,只是语法有所不同。
g。

但是,您可以编写a = (b if c)以得到所需的效果,而无需三进制。这是因为b if c如果c为假,则评估为nil 。
卡梅伦·马丁

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"

要实际super不带任何参数的调用,您需要说super()


3
如果B#helloname = 42之前有super,则说“你好42”。
安德鲁·格林

14

默认情况下,块和方法返回最后一行的值。puts为调试目的在语句末尾添加语句可能会导致不良的副作用



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

1
是的,类变量可能很棘手。我认为大多数有经验的Rubyists都会说避免使用它们是明智的,因为如果没有它们,通常还有其他方法可以解决问题。一些语言爱好者甚至会说Ruby的类变量在语言级别上设计不佳。
David J. 2010年

8

我了解到的一件事是谨慎使用运算符|| =。如果要处理布尔值,请特别小心。我通常使用|| = b作为catch所有人,如果其他所有操作都失败并且'a'保持为零,则为'a'提供默认值。但是如果a为假而b为真,则将a分配为true。


您可以使用a = b if a.nil?@a = b unless defined?(@a)
安德鲁·格林

8
  • 理解区块非常重要,它们在任何地方都被使用。

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

  • 使用抬高和抢救进行异常处理,而不是抛出和捕获。

  • 您可以使用,;但不必这样做,除非您想将多件事情放在一行上。


如果您不打算超越Ruby 1.8.6,那么请尽可能地忽略parens。否则,最好使用它们。
Mike Woodhouse,

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

刚开始,我以为我可以通过简单地执行以下操作同时拥有实例方法类方法的模块:

module Displayable
  def display
    puts name
    self.class.increment_displays
  end
  def self.number_of_displays  # WRONG!
    @number_of_displays
  end
  [...]
end

不幸的是,方法number_of_displays永远不会被包含或扩展,因为它是“模块类方法”。只能将“模块实例方法”包含在类中(作为实例方法)或扩展到类中(作为类方法)。这就是为什么需要将mixin的实例方法放入模块中,并将mixin的类方法放入另一个模块中的原因(通常将类方法放入“ ClassMethods”子模块中)。由于包括 magic方法,您可以轻松地在一个简单的“ include Displayable”调用中同时包含实例方法和类方法(如上例所示)。

这个混入将基于每个类对每个显示进行计数。该计数器是一个类属性,因此每个类都有自己的属性(如果您从Person类派生一个新类,则您的程序可能会失败,因为派生类的@number_of_displays计数器将永远不会初始化)。您可能需要将@number_of_displays替换为@@ number_of_displays以使其成为全局计数器。在这种情况下,每个类层次结构将有其自己的计数器。如果您想要一个全局且唯一的计数器,则应将其设为模块属性。

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

我仍然不知道如何干净地将其中的某些混合方法设为私有或受保护的(仅应将displaynumber_of_displays方法作为公共方法包括在内)。


7

注意范围符号。

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

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

我非常喜欢Ruby。但这点对点与点对点的东西使我感到烦恼。我认为这种微妙的双重语法“功能”即为:

  • 容易打错,并且
  • 浏览代码时很容易错过您的眼睛

应该不会在我的程序中造成毁灭性的一次性错误。


1
for (i=0; i<max; i++)for (i=0; i<=max; i++)
g

我试图找出0..10和0 ... 10之间的区别是什么。
Luis D Urraca 2012年

6

我认为Perl是“ and”和“ or”的代表,Perl是Ruby最明显的“父母”之一(最著名的是Smalltalk)。它们的优先级都比(要低于分配,实际上是指行为出自何处)低,&&||这是你应该使用的运营商。

要注意的其他事情并不是立即显而易见的:

尽管看起来有点像,但您实际上并没有调用方法/函数。而是,如在Smalltalk中一样,您将消息发送到对象。所以method_missing真的更像message_not_understood

some_object.do_something(args)

相当于

some_object.send(:do_something, args) # note the :

符号已被广泛使用。那就是那些开始的事情:它们并不是立即显而易见的(嗯,对我来说不是),但是您越早掌握它们就越好。

Ruby秉承“鸭式”的原则,遵循“如果它像鸭子一样走路而像鸭子一样嘎嘎...”这样的原理,它允许使用通用的方法子集非正式替换对象,而无需任何显式继承或混合关系。


谢谢。我讨厌send方法:它使您甚至可以在类外调用私有方法!哎哟。
MiniQuark

1
@MiniQuark:这就是我喜欢send方法的地方!
安德鲁·格林

6

如果您使用attr_writerattr_accessor(或def foo=)声明了一个setter(aka mutator),请小心从类内部调用它。由于变量是隐式声明的,因此解释程序始终必须解析foo = bar为声明名为foo的新变量,而不是调用method 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

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


5

了解时间和日期类之间的区别。两者是不同的,并且在将它们用于rails时产生了问题。Time类有时与标准ruby / rails库中存在的其他Time类库冲突。我个人花了很多时间来了解Rails应用程序中到底发生了什么。后来我想到了

Time.new

它指的是我什至不知道的某个图书馆。

对不起,如果我不清楚我想确切说什么。如果其他人也遇到过类似的问题,请重新解释。


4

过去让我感到\n困惑的一个原因是,单引号内的字符串不支持换行符()以及其他字符。反斜杠本身可以逃脱。您必须使用双引号使转义按预期工作。


1
那与其他语言有什么不同?
罗伯特·格兰伯

Java,一个。Java中的单引号只能用于包含单个字符,不能包含字符串。
约翰·托普利

1
这与任何允许您对字符串使用单引号的语言保持一致,这就是为什么这样做。
singpolyma

@John:是的,但是Java中的'\ n'仍然是换行符。
乔恩

1
但是在Java中,单引号只能创建char类型的值。不是字符串。就是这样。
jmucchiello

4
x = (true and false) # x is false

正如您指出的,0和”是正确的。

您可以具有相同名称的方法和模块/类(这很有意义,因为该方法实际上已添加到Object并因此具有其自己的名称空间)。

没有多重继承,但是经常使用“ mixin模块”将通用方法添加到多个类。


0 == true // //我脑子里的c编译器爆炸了!!
肯尼,

1
0 == true在Ruby中为false。0为true是有意义的,因为true是Ruby中的一个对象。在C 0中,恰好具有与false相同的表示形式。
Jules

在Ruby中,只有falseand nil是错误的。所有其他都是真实的价值观。
rubyprince

4

在您找到原因之前,可以重新定义方法,并且可以使方法陷入困境。(诚然,如果错误地重新定义了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

跑:

$ ruby demo.rb
=> "Hello from second definition"

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

$ ruby -w demo.rb
demo.rb:10: warning: method redefined; discarding old hello1
=> "Hello from second definition"

如果可以的话,我会+100使用警告。
Andrew Grimm

3

我认为在事情上使用.length总是很好的...因为size几乎受所有事物的支持,并且Ruby具有动态类型,当您输入错误的类型时,您可以得到真正奇怪的结果调用.size ...我宁愿得到NoMethodError:未定义的方法“ length”,所以我通常从不对Ruby中的对象调用size。

咬我不止一次

还请记住,对象具有id,所以我尽量不要使用变量调用id或object_id,以避免混淆。如果我需要一个Users对象上的ID,最好将其命名为user_id。

只是我的两分钱


2

我是红宝石的新手,在第一轮中,我遇到了一个有关将浮点数/字符串更改为整数的问题。我从花车开始,将所有内容编码为 f.to_int。但是,当我继续使用相同的方法处理字符串时,运行该程序时我弯了腰。

显然,字符串没有to_int方法,但浮点数和整数都有。

irb(main):003:0* str_val = '5.0'
=> "5.0"
irb(main):006:0> str_val.to_int
NoMethodError: undefined method `to_int' for "5.0":String
        from (irb):6
irb(main):005:0* str_val.to_i
=> 5


irb(main):007:0> float_val = 5.0
=> 5.0
irb(main):008:0> float_val.to_int
=> 5
irb(main):009:0> float_val.to_i
=> 5
irb(main):010:0>

随意的括号一开始也把我扔了。我看到有一些代码,有一些没有代码。我花了一段时间才意识到两种风格都可以接受。


2

与monkut的回应有关,Ruby to_foo方法暗示了他们将执行多严格的转换。

诸如之类的短代码to_ito_s要使其懒惰,即使它们无法以该格式正确表示,也应将它们转换为目标类型。例如:

"10".to_i == 10
:foo.to_s == "foo"

较长的显式函数(如to_intto_s表示可以将对象本地表示为该类型的数据。例如,Rational该类表示所有有理数,因此可以通过调用将其直接表示为Fixnum(或Bignum)整数to_int

Rational(20,4).to_int == 5

如果您不能调用更长的方法,则意味着该对象不能以该类型自然地表示。

因此,基本上,在转换时,如果您对方法名称不满意,Ruby将对转换不满意。


1
“懒惰”在这里合适吗?
安德鲁·格林



0

这个让我发疯了一次:

1/2 == 0.5 #=> false
1/2 == 0   #=> true

我相信这在Java,C和C ++中的行为将完全相同。
拉里

这很有趣,我什至没有考虑,但是如果您打开irb并尝试使用它,这是有道理的:(1/2)是一个Fixnum,而(0.5)是一个Float。而且我们知道Fixnim!= Float。
DemitryT 2012年

2
@DemitryT我想简单的原因是,1/2计算结果为0,这不等于0.5不分类型。但是Rational(1, 2) == 0.5,和1.0 == 1
Max Nanasy 2012年

通用语言打here在这里。这是红宝石和编程新手应该知道的。
dtc

0
1..5.each {|x| puts x}

不起作用。您必须将范围放在括号中,例如

(1..5).each {|x| puts x}

因此它认为您没有打电话5.each。我认为这是一个优先问题,就像x = true and false陷阱一样。


我将其称为括号。其次,如果任何代码看起来都有返回值/优先级问题,则无论如何都应将其括在括号中。因此,对我来说,这个“陷阱”没有什么特别的。您可以继续编写每个组合的“陷阱”,但这会浪费时间。坦率地说,即使您在此方面获得了预期的结果,我仍然希望使用括号括起来。
Özgür的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.