Что нужно знать о Ruby Gotchas, о новичке?

Недавно я изучил язык программирования 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 до конца для целей отладки может вызвать неприятные побочные эффекты

Ответ 8

У меня было много проблем с пониманием переменных класса, атрибутов класса и методов класса. Этот код может помочь новичку:

class A
  @@classvar = "A1"
  @classattr = "A2"
  def self.showvars
    puts "@@classvar => "[email protected]@classvar
    puts "@classattr => "[email protected]
  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 будет лениться с преобразованием.

Ответ 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