Недавно я изучил язык программирования Ruby, и все это хороший язык. Но я был очень удивлен, увидев, что это было не так просто, как я ожидал. Точнее, "правило наименьшего удивления" мне не показалось очень уважаемым (конечно, это довольно субъективно). Например:
x = true and false
puts x # displays true!
и знаменитый:
puts "zero is true!" if 0 # zero is true!
Каковы другие "Gotchas", которые вы предупреждали бы о новичке Ruby?
Ответ 1
Wikipedia Ruby gotchas
Из статьи:
- Имена, начинающиеся с прописной буквы, рассматриваются как константы, поэтому локальные переменные должны начинаться с строчной буквы.
- Символы
$
и @
не указывают тип данных переменной, как в Perl, а скорее работают как операторы разрешения области.
- Для обозначения чисел с плавающей запятой следует следовать с нулевой цифрой (
99.0
) или явным преобразованием (99.to_f
). Недостаточно добавить точку (99.
), потому что числа восприимчивы к синтаксису метода.
- Булевая оценка небулевых данных строгая:
0
, ""
и []
оцениваются как true
. В C выражение 0 ? 1 : 0
оценивается как 0
(т.е. False). Однако в Ruby он дает 1
, так как все числа оцениваются до true
; только nil
и false
оцениваются до false
. Следствием этого правила является то, что методы Ruby по соглашению - например, поиск по регулярному выражению - возвращают числа, строки, списки или другие недопустимые значения при успешном выполнении, но nil
при сбое (например, несоответствие). Это соглашение также используется в Smalltalk, где в булевом выражении могут использоваться только специальные объекты true
и false
.
- Версии до 1.9 не имеют характерных типов данных (сравните с C, который предоставляет тип
char
для символов). Это может вызвать неожиданности при нарезке строк: "abc"[0]
дает 97
(целое число, представляющее код ASCII первого символа в строке); для получения "a"
используйте "abc"[0,1]
(подстрока длины 1) или "abc"[0].chr
.
-
Обозначение statement until expression
, в отличие от эквивалентных операторов других языков (например, do { statement } while (not(expression));
в C/С++/...), на самом деле никогда не запускает оператор, если выражение уже true
. Это связано с тем, что statement until expression
на самом деле является синтаксическим сахаром над
until expression
statement
end
эквивалент которого в C/С++ равен while (not(expression)) statement;
точно так же, как statement if expression
является эквивалентом
if expression
statement
end
Однако обозначение
begin
statement
end until expression
в Ruby фактически выполнит оператор один раз, даже если выражение уже верно.
- Поскольку константы являются ссылками на объекты, изменение того, что константа ссылается на генерирует предупреждение, но модификация самого объекта не делает. Например,
Greeting << " world!" if Greeting == "Hello"
не генерирует ошибку или предупреждение. Это похоже на переменные final
в Java, но Ruby также имеет функцию "замораживания" объекта, в отличие от Java.
Некоторые функции, которые отличаются от других языков:
-
Обычные операторы условных выражений 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 г.) интерпретатор Ruby выдает предупреждение, которое побуждает автора не пропускать
()
, чтобы избежать двусмысленного значения кода. Не использование ()
по-прежнему является распространенной практикой, и может быть особенно полезно использовать Ruby как язык, пригодный для чтения для конкретного языка, а также метод под названием method_missing()
.
Ответ 2
У новичков будут проблемы с методами равенства:
- a == b: проверяет, равны ли а и b. Это наиболее полезно.
- a.eql? b: также проверяет, являются ли a и b равными, но иногда они более строгие (это может проверить, например, что a и b имеют один и тот же тип). Он в основном используется в хэшах.
- a.equal? b: проверяет, являются ли a и b одним и тем же объектом (проверка личности).
- a === b: используется в операторах case (я читал его как "совпадения 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
Обратите внимание, что ==, eql? и равно? всегда должны быть симметричными: если a == b, то b == a.
Также обратите внимание, что == и eql? реализованы в классе Object как псевдонимы до equal? , поэтому, если вы создаете новый класс и хотите == и 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). Как я уже сказал, вы можете читать === 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)
Оператор 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
Если вы определяете новый класс, экземпляры которого представляют какой-то контейнер или диапазон (если у него есть что-то вроде метода 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
Ответ 3
-
Патч обезьяны. Ruby имеет открытые классы, поэтому их поведение можно динамически изменять во время выполнения...
-
Объекты могут отвечать на undefined методы, если method_missing
или send
было переопределено. Это использует вызов метода на основе сообщений Ruby. Rails 'ActiveRecord система использует это для больших эффект.
Ответ 4
Следующий код меня удивил. Я думаю, что это опасная добыча: и легко набирать, и трудно отлаживать.
(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
В принципе, когда переменная определяется только внутри блока, тогда она уничтожается в конце блока, а затем она получает от reset до nil
на каждой итерации. Это обычно то, что вы ожидаете. Но если переменная определена перед блоком, тогда внешняя переменная используется внутри блока, и поэтому ее значение сохраняется между итерациями.
Одним из решений было бы написать это вместо:
comment = number%2==0 ? " is even" : nil
Я думаю, что многие люди (включая меня) склонны писать "a = b if c
" вместо "a = (c ? b : nil)
", потому что это более читаемо, но, очевидно, имеет побочные эффекты.
Ответ 5
При вызове 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()
.
Ответ 6
Блоки и методы возвращают значение последней строки по умолчанию. Добавление операторов puts
до конца для целей отладки может вызвать неприятные побочные эффекты
Ответ 7
Наследование не играет роли в определении видимости метода в Ruby.
Ответ 8
У меня было много проблем с пониманием переменных класса, атрибутов класса и методов класса. Этот код может помочь новичку:
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
Ответ 9
-
Блоки действительно важны для понимания, они используются повсеместно.
-
Вам не нужны скобки вокруг параметров метода. Используете ли вы их или нет, зависит от вас. Некоторые говорят, что вы всегда должны использовать их.
-
Используйте команду raise и rescue для обработки исключений, а не для броска и catch.
-
Вы можете использовать ;
, но вам не нужно, если вы не хотите помещать несколько элементов в одну строку.
Ответ 10
Одна вещь, которую я узнал, - это использовать оператор || = тщательно. и проявляйте особую осторожность, если вы имеете дело с булевыми. Я обычно использовал || = b как уловку, чтобы дать "а" значение по умолчанию, если все остальное не удалось, а "а" осталось нулевым. но если a является ложным и b истинно, тогда a будет присвоено значение true.
Ответ 11
У меня были проблемы с 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" ). Благодаря включенному магическому методу вы можете легко включать как методы экземпляра, так и методы класса только в одном простом вызове "include Displayable" (как показано в примере выше).
Этот mixin будет считать каждый дисплей в классе. Счетчик - это атрибут класса, поэтому каждый класс будет иметь свой собственный (ваша программа, вероятно, завершится неудачей, если вы выберете новый класс из класса Person, поскольку счетчик @number_of_displays для производного класса никогда не будет инициализирован). Вы можете заменить @number_of_displays на @@number_of_displays, чтобы сделать его глобальным счетчиком. В этом случае каждая иерархия классов будет иметь свой собственный счетчик. Если вам нужен глобальный и уникальный счетчик, вы должны, вероятно, сделать его атрибутом модуля.
Все это было определенно неинтуитивно для меня, когда я начал с Ruby.
Я до сих пор не могу понять, как сделать некоторые из этих методов mixin частными или защищенными (в качестве общедоступных методов следует включить только метод display и number_of_displays).
Ответ 12
Обратите внимание на нотацию Range.
(По крайней мере, обратите внимание больше, чем I!)
Существует разница между 0..10
(две точки) и 0...10
(три точки).
Мне нравится Ruby. Но эта точка-точка и точка-точка-точка меня обманывают. Я думаю, что такая тонкая двойная синтаксическая "особенность", которая есть:
- легко ошибиться, и
- легко пропустить глазами, глядя на код
не сможет вызывать опустошительные ошибки в моих программах.
Ответ 13
Я думаю, что "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 большой на "утиной печати", следуя принципалу, что "если он ходит как утка и шарлатанцы, как утка...", что позволяет неформальную подстановку объектов с общим подмножеством методов без какого-либо явного наследования или mixin.
Ответ 14
Если вы объявляете setter (aka mutator) с помощью attr_writer
или attr_accessor
(или def foo=
), будьте осторожны, чтобы вызвать его изнутри класса. Поскольку переменные неявно объявлены, интерпретатору всегда необходимо разрешить 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
Это также относится к объектам Rails ActiveRecord, которые получают аксессоры, определенные на основе полей в базе данных. Поскольку они не являются даже переменными экземпляра @-style, правильный способ установить эти значения в отдельности - с помощью self.value = 123
или self['value'] = 123
.
Ответ 15
Понимание разницы между классом Time и Date. Оба они отличаются друг от друга и создают проблемы при использовании их в рельсах. Класс Time иногда конфликтует с другими библиотеками классов времени, присутствующими в стандартной библиотеке ruby /rails. Мне лично потребовалось много времени, чтобы понять, что именно происходит в моем приложении rails. Позже я подумал, когда я сделал
Time.new
Это была ссылка на некоторую библиотеку в том месте, о котором я даже не подозревал.
Извините, если я не понимаю, что именно хочу сказать. Если другие сталкиваются с подобными проблемами, пожалуйста, объясните.
Ответ 16
Тот, кто поймал меня в прошлом, состоит в том, что последовательность escape-последовательности (\n
) escape-последовательности — среди других — не поддерживается строками в одинарных кавычках. Сама обратная косая черта ускользает. Вы должны использовать двойные кавычки для ускорения работы, как ожидалось.
Ответ 17
x = (true and false) # x is false
0 и '' истинны, как вы указали.
У вас может быть метод и модуль/класс с тем же именем (что имеет смысл, потому что метод фактически добавляется в Object и, следовательно, имеет собственное пространство имен).
Нет множественного наследования, но часто "mixin modules" используются для добавления общих методов к нескольким классам.
Ответ 18
Способы могут быть переопределены и могут стать ум-scratcher, пока вы не обнаружите причину. (По общему признанию, эта ошибка, вероятно, немного "сложнее", чтобы обнаружить, когда действие контроллера 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:
$ ruby demo.rb
=> "Hello from second definition"
Но назовите его с включенными предупреждениями, и вы увидите причину:
$ ruby -w demo.rb
demo.rb:10: warning: method redefined; discarding old hello1
=> "Hello from second definition"
Ответ 19
Я думаю, что всегда полезно использовать .length для вещей... так как размер поддерживается почти всем, а Ruby имеет динамические типы, вы можете получить действительно странные результаты, вызывающие .size, когда у вас неправильный тип... Я бы скорее, получите метод NoMethodError: undefined `length ', поэтому я вообще никогда не называю размер объектов в Ruby.
бит меня более одного раза.
Также помните, что объекты имеют идентификаторы, поэтому я стараюсь не использовать переменные call id или object_id, чтобы избежать путаницы. Если мне нужен идентификатор в объекте Users, лучше называть его чем-то вроде user_id.
Только мои два цента
Ответ 20
Я новичок в ruby, и в первом раунде я столкнулся с проблемой изменения float/string на целое число. Я начал с поплавков и закодировал все как f.to_int. Но когда я продолжил и использовал тот же метод для строк, я был брошен кривой, когда он пришел, чтобы запустить программу.
По-видимому, строка не имеет метода to_int, но выполняет float и 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>
Произвольная скобка тоже бросила меня. Я видел некоторый код с и некоторые без. Мне потребовалось некоторое время, чтобы понять, что оба стиля приняты.
Ответ 21
В ответ на запрос monkut методы Ruby to_foo
указывают на то, насколько строго они будут конвертироваться.
Короткие, такие как to_i
, to_s
говорят, что они ленивы и конвертируют их в целевой тип, даже если они не могут быть точно представлены в этом формате. Например:
"10".to_i == 10
:foo.to_s == "foo"
Более длинные явные функции, такие как to_int
, to_s
, означают, что объект может быть изначально представлен как этот тип данных. Например, класс Rational
представляет все рациональные числа, поэтому он может быть непосредственно представлен как целое число Fixnum (или Bignum), вызывая to_int
.
Rational(20,4).to_int == 5
Если вы не можете вызвать более длинный метод, это означает, что объект не может быть изначально представлен в этом типе.
Итак, при конвертации, если вы ленивы с именами методов, Ruby будет лениться с преобразованием.
Ответ 22
Из В Ruby почему бы не foo = true unless defined?(foo)
выполнить задание?
foo = true unless defined?(foo) #Leaves foo as nil
Потому что foo
определяется как nil
, когда вызывается defined?(foo)
.
Ответ 23
Итерация по рубиновым хешам не гарантируется в любом конкретном порядке. (Это не ошибка, это особенность)
Hash#sort
полезен, если вам нужен определенный порядок.
Связанный вопрос: Почему массив ключей и значений из массива Rubys из 1000 хэшей всегда находится в определенном порядке?
Ответ 24
1..5.each {|x| puts x}
не работает. Вы должны поместить диапазон в круглые скобки, например
(1..5).each {|x| puts x}
поэтому он не думает, что вы вызываете 5.each
. Я думаю, что это вопрос приоритета, как и x = true and false
gotcha.
Ответ 25
Это меня разозлило однажды:
1/2 == 0.5 #=> false
1/2 == 0 #=> true