Хорошая практика, чтобы избежать изменения параметров метода в Ruby? - PullRequest
0 голосов
/ 28 июня 2018

Я понимаю, что хорошей практикой является избегать изменения параметров метода в Ruby (если это не является намерением).

Что такое простое эмпирическое правило (с точки зрения стиля, соглашений об именах и т. Д.), Как мне следует управлять этим?

Например, это хорошая практика?

def array_of_three(a, b, c)
   d = a.dup
   e = b.dup
   f = c.dup
   # Do work on d, e and f
   return [d, e, f]
end

Как мне назвать параметры метода по сравнению с дубликатами внутри функции, которые могут быть изменены? (Предполагая, что что-то вроде вышеописанного подхода является «правильным»!)

Спасибо.

Ответы [ 3 ]

0 голосов
/ 29 июня 2018

Я знаю, что это ответ, основанный на моем мнении, но я предпочитаю "отлавливать" аргументы в ruby ​​(не прибегая к .dup или подобным), это передавать их функции, которая передает аргументы лямбда, которая оценивается на месте:

def argument_trap(*args)
  lambda { args }.call
end

def array_of_three(a, b, c)
  a, b, c = argument_trap(a, b, c)
  a += 1
  b -= 1
  c *= 2
  [a, b, c]
end

def array_of_n(*args)
  args = argument_trap(*args)
  args.map { |arg| arg += arg }
end

a, b, c, d, e, f = 1, 2, 3, 4, 5, 6

three = array_of_three(a, b, c)
n = array_of_n(a, b, c, d, e, f)

p [a, b, c]
p three
puts
p [a, b, c, d, e, f]
p n

Придает

[1, 2, 3]
[2, 1, 6]

[1, 2, 3, 4, 5, 6]
[2, 4, 6, 8, 10, 12]

Демо-версия здесь

С этим шаблоном в первой строке тела функции ясно, что имена аргументов теперь указывают на клоны. Но опять же, это вопрос предпочтений - мне нравится сохранять исходные имена аргументов вместо того, чтобы называть их dupA или что-то в этом роде, поскольку для меня ясно, что в этих функциях я сейчас работаю над дубликатами.

Это также мешает функциям выполнять обе: работу со ссылками на исходное значение и с дубликатами, что, на мой взгляд, является преимуществом, поскольку выполнение обоих может привести к путанице в коде. С другой стороны, можно забыть один или два аргумента, и он снова в аду кодирования.

0 голосов
/ 29 июня 2018

Это скорее расширенный комментарий к другим ответам, чем сам по себе ответ, но если вы собираетесь изменять данные, которые вам не принадлежат, имя метода должно заканчиваться на !. Там нет причудливый синтаксис для этого; ! - это только допустимый символ для завершения метода. Например, посмотрите на этот код:

a = [1, 2, 2, 3, 3, 3]
puts a.uniq
puts a

Это выдаст [1, 2, 3], затем [1, 2, 2, 3, 3, 3], как и следовало ожидать. С другой стороны, этот код:

a = [1, 2, 2, 3, 3, 3]
puts a.uniq!
puts a

будет выводить [1, 2, 3] дважды, потому что он изменил a на месте. Это особенно приятно, если верны две вещи:

  1. Вы могли бы одинаково верно иметь не мутирующую и мутирующую версию функции
    • например. pop не очень подходит для этого паттерна, потому что как вы поп, не мутировав?
  2. Есть разумная необходимость в обоих.
    • Это немного нечетко, но , как правило, верно для вещей, представляющих операцию с некоторыми данными - вы, вероятно, хотите, чтобы она могла работать как с данными, не изменяя их, так и изменять их в место, для эффективности или просто избегая самостоятельного назначения или временных переменных.

Это не жесткое правило, но это хороший стиль для принятия, потому что он позволяет сразу увидеть, что объект мутирует.

Обратите внимание, что если бы a уже уже был всеми уникальными элементами (например, a = [4, 5, 6]), то uniq! вернул бы nil, как и стандарт для функций модификации на месте, которые не в конечном итоге выполнить модификацию. Я бы посоветовал, что если вы примете стиль !, вы также примете это, чтобы избежать путаницы при сравнении с ментальными моделями людей о том, как работают ! -функции.

0 голосов
/ 29 июня 2018

"Хорошая практика" Руби сводится к тому, чтобы не изменять данные, которыми вы не владеете. Проблема в том, что владение может быть несколько туманным, поэтому, если вы сомневаетесь, вы захотите предположить, что вы не владеете данными и сделаете копию , если , вам нужно изменить ее.

Пример плохого поведения проявляется в следующем примере:

a = [ 1, 2, 3 ]

do_stuff(a) # Works
do_stuff(a) # Doesn't do anything for some reason

a
# => [ ]    # Hey! Who did that?

Где, скажем, do_stuff был придурком и сделал это:

def do_stuff(a)
  while (v = a.pop)
    puts a
  end
end

Это повреждает приводимые вами аргументы, что плохо. Вместо этого вы должны либо использовать неразрушающий подход:

def do_stuff(a)
  a.each do |v|
    puts v
  end
end

Или вместо этого сделайте копию, если необходимо манипулировать этой структурой, например:

def do_stuff(a)
  a = a.uniq.sort
  while (v = a.pop)
    puts v
  end
end

Хороший способ проверить, что ваши методы работают должным образом, - передать им замороженные данные в тестах:

a = [ 1, 2, 3 ].freeze

do_stuff(a) # Fails trying to manipulate frozen object

Я склонен писать модульные тесты с агрессивно замороженными данными (например, deep_freeze), чтобы гарантировать, что аргументы не могут быть изменены. Это очень быстро избавляет от проблем с владением.

Если ваша функция имеет сигнатуру метода, подобную этой:

def do_stuff(*a, **b)
  # ...
end

Тогда вы будете владеть как a (varargs), так и b (kwargs), поскольку они созданы специально для вашего метода. Во всех остальных случаях вам нужно быть осторожным. Именно здесь программирование по контракту важно, так как вызывающая сторона вашего метода должна знать, какие аргументы переданы, а какие нет. Документация должна прояснить это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...