О каких рубиновых готах нужно предупреждать новичку? [закрыто]

Я недавно выучил язык программирования Ruby, и в целом это хороший язык. Но я был очень удивлен, увидев, что все оказалось не так просто, как я ожидал. Точнее, «правило наименьшего удивления» мне не показалось очень уважаемым (конечно, это весьма субъективно). Например:

x = true and false
puts x  # displays true!

и знаменитый:

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

О каких других "Gotchas" вы бы предупредили новичка из Ruby?

104 голоса | спросил 4 revs, 4 users 100%
MiniQuark
1 Jam1000000amThu, 01 Jan 1970 03:00:00 +030070 1970, 03:00:00

25 ответов


0

Wikipedia Ruby gotchas

Из статьи:

  • Имена, начинающиеся с заглавной буквы, рассматриваются как константы, поэтому локальные переменные должны начинаться со строчной буквы.
  • Символы $ и @ не указывайте переменный тип данных, как в Perl, а скорее как операторы разрешения контекста.
  • Чтобы обозначить числа с плавающей запятой, нужно следовать с нулевой цифрой (99.0) или явным преобразованием (99.to_f). Недостаточно добавить точку (99.), поскольку числа подвержены синтаксису метода.
  • Булева оценка небулевых данных является строгой: 0, "" и [] оцениваются как true. В C выражение 0 ? 1 : 0 оценивается как 0 (т.е. ложно). В 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 /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
    

    в Ruby фактически запустит оператор один раз, даже если выражение уже истинно.

  • Поскольку константы являются ссылками на объекты, изменение того, на что ссылается константа, создает предупреждение, а изменение самого объекта - нет. Например, Greeting << " world!" if Greeting == "Hello" не генерирует ошибку или предупреждение. Это похоже на переменные final в Java, но в отличие от 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 г.) интерпретатор Ruby выдает предупреждение, которое призывает автора не опускать (), чтобы избежать двусмысленного значения кода. Отказ от использования () по-прежнему является обычной практикой, и может быть особенно полезно использовать Ruby как понятный человеку язык программирования для конкретных областей, наряду с метод называется method_missing().
ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

У новичков будут проблемы с методами равенства :

  • a == b : проверяет, равны ли a и b. Это самое полезное.
  • a.eql? b : также проверяет, равны ли a и b, но иногда это является более строгим (например, может проверить, что a и b имеют одинаковый тип). Он в основном используется в хешах.
  • a.equal? b : проверяет, являются ли a и b одним и тем же объектом (проверка личности).
  • a === b : используется в операторах case (я читаю его как " a соответствует 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 как псевдонимы равно? , поэтому, если вы создаете новый Если вы хотите, чтобы == и 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 ). Как я уже сказал, вы можете прочитать a === b как "a match 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? или 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
ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0
ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Следующий код удивил меня. Я думаю, что это опасный вопрос: и с ним легко столкнуться, и с ним трудно отладить.

(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) ", потому что он более читабелен, но, очевидно, имеет побочные эффекты.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

При вызове 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().

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Блоки и методы возвращают значение последней строки по умолчанию. Добавление операторов puts в конец для целей отладки может вызвать неприятные побочные эффекты

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0
ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

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

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
ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Одна вещь, которую я узнал, это осторожно использовать оператор || =. и будьте особенно внимательны, если вы имеете дело с логическими значениями. я обычно использовал a || = b в качестве перехвата all, чтобы дать «a» значение по умолчанию, если все остальное не удалось, а «a» остался нулевым. но если a равно false и b равно true, тогда a будет присвоено значение true.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0
  • Блоки действительно важны для понимания, они используются везде.

  • Вам не нужны скобки вокруг параметров метода. Используете ли вы их или нет, зависит от вас. Некоторые говорят, что вы всегда должны их использовать .

  • Используйте повышение и спасение для обработки исключений, а не для броска и захвата.

  • Вы можете использовать ;, но вам это не нужно, если вы не хотите поместить несколько вещей в одну строку.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

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

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 никогда не будет включен или расширен, потому что это «метод класса модуля». Только «методы экземпляра модуля» могут быть включены в класс (как методы экземпляра) или расширены в класс (как методы класса). Вот почему вам нужно поместить методы экземпляра вашего миксина в модуль, а методы класса вашего миксина - в другой модуль (вы обычно помещаете методы класса в подмодуль "ClassMethods"). Благодаря магическому методу includes вы можете легко включить методы экземпляра и методы класса в один простой вызов «include Displayable» (как показано в примере выше).

Этот миксин будет подсчитывать каждое отображение на основе для класса . Счетчик является атрибутом класса, поэтому каждый класс будет иметь свой собственный (ваша программа, вероятно, потерпит неудачу, если вы извлечете новый класс из класса Person, поскольку счетчик @number_of_displays для производного класса никогда не будет инициализирован ). Вы можете заменить @number_of_displays на @@ number_of_displays , чтобы сделать его глобальным счетчиком. В этом случае у каждой иерархии классов будет свой счетчик. Если вам нужен глобальный и уникальный счетчик, вы, вероятно, должны сделать его атрибутом модуля.

Все это определенно не было интуитивно понятным для меня, когда я начал работать с Ruby.

Я до сих пор не могу понять, как правильно сделать некоторые из этих методов mixin частными или защищенными (в качестве открытых методов должны быть включены только методы display и number_of_displays ).

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Обратите внимание на обозначение диапазона.

(По крайней мере, уделите больше внимания, чем изначально я !)

Существует разница между 0..10 (две точки) и 0...10 (три точки).

Мне очень нравится Ruby. Но эта точка-точка против точки-точка-точка меня беспокоит. Я думаю, что такая тонкая двойная синтаксическая "особенность", которая:

  • легко набирать опечатки и
  • легко пропустить глазами, просматривая код

не должен вызывать ужасных ошибок в моих программах.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Я думаю "and" и "or "- это дань Perl, который является одним из наиболее очевидных« родителей »Руби (самый выдающийся из них - Smalltalk). Они оба имеют гораздо более низкий приоритет (фактически ниже, чем присвоение, из которого и происходит отмеченное поведение), чем && и || какие операторы вы должны использовать.

Другие вещи, о которых нужно знать, не сразу очевидны:

Вы на самом деле не вызываете методы /функции, хотя это выглядит так. Вместо этого, как и в Smalltalk, вы отправляете сообщение объекту. Так что method_missing действительно больше похоже на message_not_understood.

some_object.do_something(args)

эквивалентно

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

Символы очень широко используются. Это те вещи, которые начинаются с :, и они не сразу очевидны (ну, не для меня), но чем раньше вы доберетесь до с ними лучше.

Руби хорошо разбирается в «типизировании утки», следуя принципу, что «если он ходит как утка и крякает как утка ...», который позволяет неформально заменять объекты общим подмножеством методов без какого-либо явного наследования смешанные отношения.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Если вы объявите сеттер (он же мутатор), используя 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, которые получают средства доступа, определенные на основе полей в базе данных. Так как они даже не являются переменными экземпляра в стиле @, правильный способ установить эти значения по отдельности - с помощью self.value = 123 или self['value'] = 123

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Понимание разницы между временем и классом даты. Оба разные и создали проблемы при использовании их в рельсах. Класс Time иногда конфликтует с другими библиотеками классов Time, присутствующими в стандартной библиотеке ruby ​​/rails. Лично мне потребовалось много времени, чтобы понять, что именно происходит в моем приложении rails. Позже я понял, когда я сделал

Time.new

Это была ссылка на какую-то библиотеку в месте, о котором я даже не знал.

Извините, если мне не ясно, что именно я хочу сказать. Если другие сталкивались с подобными проблемами, пожалуйста, повторите объяснение.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

В прошлом меня поразило то, что escape-последовательность символа новой строки (\n), среди прочего, не поддерживается по строкам в одинарных кавычках. Сама обратная косая черта экранируется. Вы должны использовать двойные кавычки, чтобы выход работал так, как ожидалось.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0
x = (true and false) # x is false

0 и '' истинны, как вы указали.

Вы можете иметь метод и модуль /класс с одинаковым именем (что имеет смысл, потому что метод фактически добавляется в Object и, таким образом, имеет свое собственное пространство имен).

Нет множественного наследования, но часто «смешанные модули» используются для добавления общих методов к нескольким классам.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Методы могут быть переопределены и могут стать разумом, пока вы не выясните причину. ( Следует признать, что эту ошибку, вероятно, немного сложнее обнаружить, когда действие контроллера 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"
ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Я думаю, что всегда полезно использовать .length в вещах ... так как размер поддерживается практически всем, а в Ruby есть динамические типы, вы можете получить действительно странные результаты, вызывая .size, когда у вас неправильный тип ... гораздо лучше получить NoMethodError: undefined метод `length ', поэтому я обычно никогда не вызываю size для объектов в Ruby.

кусал меня не раз.

Также помните, что у объектов есть идентификаторы, поэтому я стараюсь не использовать переменные, вызывающие id или object_id, просто чтобы избежать путаницы. Если мне нужен идентификатор для объекта Users, лучше всего назвать его как user_id.

Просто мои два цента

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Я новичок в ruby, и в моем первом раунде я столкнулся с проблемой, касающейся замены чисел с плавающей точкой на целое. Я начал с поплавков и закодировал все как 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>

Произвольные скобки поначалу меня тоже бросили. Я видел некоторый код с, а некоторые без. Мне потребовалось некоторое время, чтобы понять, что оба стиля принимаются.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Относительно ответа 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 будет лениво с конвертацией.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

От в Ruby почему не foo = true unless defined?(foo) сделать назначение?

foo = true unless defined?(foo) #Leaves foo as nil

Поскольку foo определяется как nil когда вызывается defined?(foo).

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Итерации по рубиновым хэшам не гарантируются в каком-либо конкретном порядке. (Это не ошибка, это особенность)

Hash#sort полезно, если вам нужен определенный заказ.

Схожий вопрос: Почему массив Ruby из 1000 пар ключей и значений хэшей всегда в определенном порядке?

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0

Это однажды разозлило меня:

1/2 == 0.5 #=> false
1/2 == 0   #=> true
ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17
0
1..5.each {|x| puts x}

не работает. Вы должны поместить диапазон в круглые скобки, например,

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

, поэтому он не думает, что вы звоните 5.each. Я думаю, что это проблема приоритета, точно так же как x = true and false.

ответил JacobIRR 20 AMpFri, 20 Apr 2018 00:36:17 +030036Friday 2018, 00:36:17

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132