什么是Ruby新手应该警告一个新手?
最近我学习了Ruby编程语言,总而言之,这是一门很好的语言。 但是我很惊讶地看到,这并不像我预料的那么简单。 更确切地说,“最less突击规则”对我来说似乎不是很受尊重(当然这是相当主观的)。 例如:
x = true and false puts x # displays true!
和着名的:
puts "zero is true!" if 0 # zero is true!
你会警告一个Ruby新手呢,还有什么其他的“Gotchas”?
维基百科Ruby陷入困境
从文章:
- 以大写字母开头的名称被视为常量,因此局部variables应以小写字母开头。
- 字符
$
和@
不象Perl那样指示variables数据types,而是作为范围parsing操作符。 - 要表示浮点数,必须使用零位(
99.0
)或显式转换(99.to_f
)。 追加一个点(99.
)是不够的,因为数字容易受到方法语法的影响。 - 非布尔数据的布尔评估是严格的:
0
,""
和[]
都评估为true
。 在C中,expression式0 ? 1 : 0
0 ? 1 : 0
评估为0
(即false)。 然而,在Ruby中,因为所有的数字都是true
,所以得到1
。 只有nil
和false
评价为false
。 这个规则的必然结果是,按照惯例,Ruby方法(例如,正则expression式search)在成功时返回数字,string,列表或其他非错误值,但是在失败(例如,不匹配)时返回nil
。 这个约定也用在Smalltalk中,其中只有特殊对象true
和false
可以用在布尔expression式中。 - 1.9之前的版本缺less字符数据types(与C相比,它为字符提供了
char
types)。 当切分string时,这可能会导致意外:"abc"[0]
产生97
(一个整数,表示string中第一个字符的ASCII码)。 获得"a"
使用"abc"[0,1]
(长度为1的子string)或"abc"[0].chr
。 -
不像其他语言的等效语句(例如,
do { statement } while (not(expression));
在C / C ++ / …中),statement until expression
的符号statement until expression
实际上从不运行语句,如果expression式已经为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
在Ruby中实际上会运行语句,即使expression式已经是真的。
- 因为常量是对象的引用,所以改变一个常量引用会产生一个警告,但修改对象本身不会。 例如,
Greeting << " world!" if Greeting == "Hello"
Greeting << " world!" if Greeting == "Hello"
不会产生错误或警告。 这与Java中的final
variables类似,但Ruby不像Java那样具有“冻结”对象的function。
一些与其他语言显着不同的function:
-
条件expression式的常用运算符
and
和or
不遵循正常的优先级规则:and
不会比“or
更紧密地绑定。 Ruby也有expression式运算符||
和&&
按预期工作。 -
def
里面的def
不会做Python程序员可能会想到的:def a_method x = 7 def print_x; puts x end print_x end
这给出了关于
x
没有被定义的错误。 你需要使用一个Proc
。
语言function
- 方法参数周围省略括号可能会导致意想不到的结果,如果方法采用多个参数。 Ruby开发人员指出,在未来的Ruby版本中可能会忽略多参数方法中的括号的省略; 目前(2007年11月)Ruby解释器抛出一个警告,鼓励作者不要忽略
()
,以避免代码含义模糊。 不使用()
仍然是很常见的做法,特别是使用Ruby作为人类可读的领域特定的编程语言本身,以及method_missing()
方法。
新手们在平等方面会遇到麻烦:
- a == b :检查a和b是否相等。 这是最有用的。
- a.eql? b :还检查a和b是否相等,但有时更严格(例如,可能会检查a和b是否具有相同的types)。 它主要用于哈希。
- a.equal? b :检查a和b是否是同一个对象(身份检查)。
- a === b :在case语句中使用(我把它看作“ a matches 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中实现为别名等于? ,所以如果你创build一个新的类,并希望==和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
===方法的行为不同。 首先它不是对称的(a === b并不意味着b === a)。 正如我所说的,你可以阅读一个===乙作为“一场比赛乙”。 这里有一些例子:
# === 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语句基于===方法:
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
如果你定义了一个新的类,它的实例代表某种types的容器或者范围(如果它有类似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
-
猴子补丁 。 Ruby有开放的类,所以它们的行为可以在运行时dynamic改变。
-
如果
method_missing
或send
已被覆盖,对象可能会响应未定义的方法 。 这利用了Ruby的基于消息的方法调用。 Rails的ActiveRecord系统使用这个效果很好。
下面的代码让我感到吃惊。 我认为这是一个危险的问题:既容易碰到,又很难debugging。
(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
基本上,当一个variables只在一个块内被定义时,它在块的末尾被销毁,然后在每次迭代时被重置nil
。 这通常是你所期望的。 但是,如果variables是在块之前定义的,那么外部variables在块内部使用,因此其值在迭代之间是持久的。
一个解决办法是写这个:
comment = number%2==0 ? " is even" : nil
我认为很多人(包括我)倾向于写“ a = b if c
”而不是“ a = (c ? b : nil)
”,因为它更具可读性,但显然有副作用。
当没有参数的情况下调用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()
。
块和方法默认返回最后一行的值。 将puts
语句添加到最后以进行debugging可能会导致不愉快的副作用
inheritance在Ruby 中确定方法可见性不起作用。
理解类variables,类属性和类方法时遇到了很多困难。 这个代码可能会帮助新手:
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
我学到的一件事就是仔细地使用|| =操作符。 如果你正在处理布尔值,要格外小心。 如果一切都失败了,'a'仍然是零,我通常用一个|| = b作为一个catch来给出一个默认值。 但是如果a是假的,b是真的,那么a将被分配为真。
-
块是非常重要的理解,他们到处使用。
-
方法参数周围不需要括号。 你是否使用它取决于你。 有人说你应该总是使用它们 。
-
使用raise和rescue来处理exception,而不是抛出和捕获。
-
你可以使用
;
但是除非你想把多个东西放在一条线上,否则你不需要。
我遇到了包含实例方法和类方法的mixins问题。 这个代码可能会帮助新手:
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”子模块中)。 由于包含了魔术方法,您可以很容易地将实例方法和类方法包含在一个简单的“包含Displayable”调用中(如上例所示)。
这个mixin将按照每个class级计算每个显示。 计数器是一个类属性,所以每个类都有它自己的(如果从Person类派生一个新类,程序可能会失败,因为派生类的@number_of_displays计数器将永远不会被初始化)。 您可能希望用@@ number_of_displaysreplace@number_of_displays ,使其成为全局计数器。 在这种情况下,每个类层次结构都有自己的计数器。 如果你想要一个全局唯一的计数器,你应该把它作为一个模块属性。
当我开始使用Ruby时,所有这些对我来说绝对是不直观的。
我仍然不知道如何干净地使这些mixin方法中的一些私有或受保护(只有display和number_of_displays方法应该被包含为公共方法)。
注意范围表示法。
(至less,比我最初多加注意!)
0..10
(两个点)和0...10
(三个点)之间是有区别的。
我非常喜欢Ruby。 但是,这个点点对点点的事情让我感到困扰。 我认为这样一个微妙的双语法“function”是:
- 容易打错,而且
- 容易错过你的眼睛,而看着代码
不应该能够在我的程序中造成破坏性的错误。
我认为“ 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在“鸭子打字”方面很重要,遵循“如果像鸭子一样走路,像鸭子一样跳跃……”这样的原则,它允许用一个公共子集的方法来非对称地replace对象,而没有任何明确的inheritance关系或混合关系。
如果使用attr_writer
或attr_accessor
(或def foo=
)声明setter(aka mutator),请小心从类中调用它。 由于variables是隐含声明的,所以解释器总是必须parsingfoo = bar
来声明一个名为foo的新variables,而不是调用方法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对象,这些对象根据数据库中的字段获取访问者。 由于它们甚至不是@样式的实例variables,所以分别设置这些值的正确方法是使用self.value = 123
或self['value'] = 123
。
了解时间和date类别之间的差异。 两者都是不同的,并在轨道中使用它们时产生了问题。 Time类有时会与标准ruby / rails库中的其他Time类库冲突。 它个人花了我很多时间来了解我的Rails应用程序到底发生了什么。 后来,我想到了当我做了
Time.new
这是指一个我甚至不知道的地方的一些图书馆。
对不起,如果我不清楚我想说什么。 如果别人遇到类似的问题,请重新解释。
一个在过去引起我注意的是换行字符( \n
)转义序列 – 除了其他字符外,并没有被单引号内的string所支持。 反斜杠本身得到逃脱。 您必须使用双引号才能按预期工作。
x = (true and false) # x is false
0和''是正确的,正如你所指出的那样。
你可以有一个方法和一个模块/类相同的名称(这是有道理的,因为该方法实际上被添加到对象,从而有自己的命名空间)。
没有多重inheritance,但是经常使用“混入模块”将通用方法添加到多个类中。
方法可以重新定义,并可以成为一个思想的抓手,直到你发现原因。 ( 不可否认,当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"
我认为在事物上使用.length总是很好的…因为几乎所有东西都支持size,而Ruby有dynamictypes,所以当你的types错误的时候,调用.size的时候会得到很奇怪的结果…我宁愿得到一个NoMethodError:未定义的方法`长度',所以我一般不会在Ruby中调用对象的大小。
我不止一次地咬了我一口。
还记得对象有ids,所以我尽量不要使用variablescall id或object_id来避免混淆。 如果我需要一个用户对象的ID,最好把它叫做user_id。
只是我的两分钱
我是新来的ruby,在我的第一轮,我遇到了一个问题,关于更改浮点数/string为一个整数。 我开始使用浮动,并将所有内容都编码为f.to_int 。 但是当我继续使用和string相同的方法时,我在运行程序时被抛出了一条曲线。
另外一个string没有to_int方法,但浮动和ints做。
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>
最初任意的括号也把我扔了。 我看到一些代码和一些没有。 我花了一段时间才意识到,无论哪种风格都被接受。
与monkut的回应有关,Ruby的to_foo
方法暗示了他们将要执行的转换有多严格。
像to_i
, to_s
这样的短to_i
它是懒惰的,并将它们转换为目标types,即使它们不能以这种格式准确表示。 例如:
"10".to_i == 10 :foo.to_s == "foo"
像to_int
, to_s
这样的更长的显式函数意味着该对象可以本地表示为该types的数据。 例如, Rational
类表示所有有理数,因此可以通过调用to_int
直接表示为Fixnum(或Bignum)整数。
Rational(20,4).to_int == 5
如果不能调用较长的方法,则意味着该对象不能以该types本地表示。
所以基本上,在转换的时候,如果你懒于使用方法名,Ruby将会懒惰的转换。
从Ruby中为什么不会foo = true unless defined?(foo)
进行赋值?
foo = true unless defined?(foo) #Leaves foo as nil
因为foo
被定义为nil
时defined?(foo)
被调用。
ruby哈希迭代不保证以任何特定的顺序发生。 (这不是一个错误,这是一个function)
Hash#sort
是有用的,如果你需要一个特定的顺序。
相关的问题: 为什么Ruby的1000个散列的键和值对总是按特定顺序排列的?
这一次让我生气了一次:
1/2 == 0.5 #=> false 1/2 == 0 #=> true
1..5.each {|x| puts x}
不起作用。 你必须把范围放在括号里面
(1..5).each {|x| puts x}
所以它不认为你打电话5.each
。 我认为这是一个优先问题,就像x = true and false
。