Передача хэшей вместо параметров метода

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

def my_method(width, height, show_border)
my_method(400, 50, false)

вы можете сделать это следующим образом:

def my_method(options)
my_method({"width" => 400, "height" => 50, "show_border" => false})

Я хотел бы узнать ваше мнение об этом. Это хорошая или плохая практика, мы должны это делать или нет? В какой ситуации эта практика действительна, и в какой ситуации это может быть опасно?

Ответ 1

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

Мое общее правило заключается в том, что если у вас есть много аргументов для метода (более 3 или 4) или множество необязательных аргументов, тогда используйте хэш-параметры, иначе использующие стандартные аргументы. Однако при использовании хэш-параметров важно всегда включать комментарий с определением метода, описывающим возможные аргументы.

Ответ 2

Ruby имеет неявные хэш-параметры, поэтому вы также можете написать

def my_method(options = {}) 

my_method(:width => 400, :height => 50, :show_border => false)

а с помощью Ruby 1.9 и нового синтаксиса хэша может быть

my_method( width: 400, height: 50, show_border: false )

Когда функция принимает более 3-4 параметров, гораздо проще увидеть, что это такое, не считая соответствующих позиций.

Ответ 3

Я бы сказал, что если вы либо:

  • Наличие более 6 параметров метода
  • Параметры передачи, которые имеют некоторые необходимые, некоторые необязательные и некоторые со значениями по умолчанию

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

Для тех из вас, кто говорит, что трудно понять, какие варианты использует метод, это просто означает, что код плохо документирован. С помощью YARD вы можете использовать тег @option для указания параметров:

##
# Create a box.
#
# @param [Hash] options The options hash.
# @option options [Numeric] :width The width of the box.
# @option options [Numeric] :height The height of the box.
# @option options [Boolean] :show_border (false) Whether to show a
#   border or not.
def create_box(options={})
  options[:show_border] ||= false
end

Но в этом конкретном примере есть такие несколько и простых параметров, поэтому я думаю, что я бы пошел с этим:

##
# Create a box.
#
# @param [Numeric] width The width of the box.
# @param [Numeric] height The height of the box.
# @param [Boolean] show_border Whether to show a border or not.
def create_box(width, height, show_border=false)
end

Ответ 4

Я думаю, что этот метод передачи параметров намного яснее, когда имеется несколько пара параметров или когда имеется ряд необязательных параметров. Это, по сути, делает вызовы методов явно самодокументированными.

Ответ 5

В Ruby нет обычной практики использовать хеш, а не формальные параметры.

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

Если у вас есть несколько аргументов для вашего метода или функции, тогда явным образом объявляю их и передаю их. Вы получаете то преимущество, что интерпретатор проверяет, что вы передали все аргументы.

Не злоупотребляйте языковой функцией, знайте, когда ее использовать и когда ее не использовать.

Ответ 6

Преимущество использования параметра Hash as заключается в том, что вы удаляете зависимость от числа и порядка аргументов.

На практике это означает, что позже вы сможете гибко реорганизовать/изменить свой метод, не нарушая совместимость с кодом клиента (и это очень хорошо при создании библиотек, потому что вы фактически не можете изменить код клиента).

(Sandy Metz "Практический объектно-ориентированный дизайн в Ruby" - отличная книга, если вы заинтересованы в разработке программного обеспечения в Ruby)

Ответ 7

Это хорошая практика. Вам не нужно думать о сигнатуре метода и порядке аргументов. Другим преимуществом является то, что вы можете легко опустить аргументы, которые вы не хотите вводить. Вы можете взглянуть на инфраструктуру ExtJS, поскольку она широко использует этот тип аргументов.

Ответ 8

Это компромисс. Вы теряете некоторую ясность (как узнать, какие параметры должны пройти) и проверили (я передал правильное количество аргументов?) И получили гибкость (метод может по умолчанию не получать, мы можем развернуть новую версию, которая принимает больше параметров и не сломать существующий код)

Вы можете увидеть этот вопрос как часть более крупного обсуждения типа Strong/Weak. Смотрите здесь блог Стива yegge. Я использовал этот стиль в C и С++ в тех случаях, когда я хотел поддерживать довольно гибкую передачу аргументов. Вероятно, стандартный HTTP GET, с некоторыми параметрами запроса именно этот стиль.

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

Ответ 9

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

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

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

Я сделал некоторые оптимизации Perl, и такие вещи могут стать заметными во внутренних циклах кода.

Функциональные параметры работают намного лучше.

Ответ 10

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

Одна (и, возможно, единственная) причина использования опций заключается в том, что функция получает аргументы, которые не обрабатываются, а просто переходят к другой функции.

Вот пример, который иллюстрирует, что:

def myfactory(otype, *args)
  if otype == "obj1"
    myobj1(*args)
  elsif otype == "obj2"
    myobj2(*args)
  else
    puts("unknown object")
  end
end

def myobj1(arg11)
  puts("this is myobj1 #{arg11}")
end

def myobj2(arg21, arg22)
  puts("this is myobj2 #{arg21} #{arg22}")
end

В этом случае "myfactory" даже не знает аргументов, требуемых "myobj1" или "myobj2". "myfactory" просто передает аргументы "myobj1" и "myobj2", и они несут ответственность за их проверку и обработку.

Ответ 11

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

Пример

class Example

def initialize(args = {})

  @code

  code = args[:code] # No error but you have no control of the variable initialization. the default value is supplied by Hash

  @code = args.fetch(:code) # returns IndexError exception if the argument wasn't passed. And the program stops

  # Handling the execption

  begin

     @code = args.fetch(:code)

  rescue 

 @code = 0

  end

end