Pasar hashes en lugar de parámetros de método [cerrado]

78

Veo que en Ruby (y en los lenguajes tipados dinámicamente, en general) una práctica muy común es pasar un hash, en lugar de declarar parámetros de método concretos. Por ejemplo, en lugar de declarar un método con parámetros y llamarlo así:

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

puedes hacerlo de esta manera:

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

Me gustaría saber tu opinión al respecto. ¿Es una buena o una mala práctica, debemos hacerlo o no? ¿En qué situación es válido utilizar esta práctica y en qué situación puede ser peligrosa?

rmaruszewski
fuente
11
Una pequeña nota: {width => 400, height => 50, show_border => false}no es una sintaxis ruby ​​válida. Creo que te refieres a {:width => 400, :height => 50, :show_border => false}o {width: 400, height: 50, show_border: false}(este último solo es válido en ruby ​​1.9.1)
sarahhodne

Respuestas:

28

Ambos enfoques tienen sus propias ventajas y desventajas, cuando usa un hash de opciones que reemplaza los argumentos estándar, pierde claridad en el código que define el método, pero gana claridad cada vez que usa el método debido a los pseudo parámetros creados mediante el uso de un hash de opciones.

Mi regla general es que si tiene muchos argumentos para un método (más de 3 o 4) o muchos argumentos opcionales, use un hash de opciones; de lo contrario, use argumentos estándar. Sin embargo, cuando se utiliza un hash de opciones, es importante incluir siempre un comentario con la definición del método que describa los posibles argumentos.

Benmcredmond
fuente
1
Ahora, si tuviera un hash de opciones, ¿podría simplemente poner el hash completo o tengo que pasar las claves => valores individualmente?
FilBot3
1
Es cierto que ambos tienen ventajas y desventajas, sin embargo no estoy de acuerdo con la regla general. Creo que las opciones son, en general, una mala práctica y deben usarse solo si esa es la única forma de resolver un problema.
Dragan Nikolic
42

Ruby tiene parámetros hash implícitos, por lo que también puede escribir

def my_method(options = {}) 

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

y con Ruby 1.9 y la nueva sintaxis hash puede ser

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

Cuando una función toma más de 3-4 parámetros, es mucho más fácil ver cuál es qué, sin contar las posiciones respectivas.

MBO
fuente
A partir de Ruby 2.7 ahora, los parámetros de hash implícitos están en desuso y se eliminarán en Ruby 3.0, lo que significa que necesitamos de nuevo {…} explícitamente pasar un hash ( ver ). La sintaxis de Ruby 1.9 que se muestra aquí todavía es posible para llamar a un método con parámetros de palabras clave .
tanius
8

Yo diría que si eres:

  1. Tener más de 6 parámetros de método
  2. Pasar opciones que tienen algunas obligatorias, otras opcionales y otras con valores predeterminados

Lo más probable es que desee utilizar un hash. Es mucho más fácil ver lo que significan los argumentos sin buscar en la documentación.

Para aquellos de ustedes que dicen que es difícil averiguar qué opciones toma un método, eso solo significa que el código está mal documentado. Con YARD , puede usar la @optionetiqueta para especificar opciones:

##
# 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

Pero en ese ejemplo específico hay tan pocos y simples parámetros, así que creo que iría con esto:

##
# 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
Sarahhodne
fuente
3

Creo que este método de paso de parámetros es mucho más claro cuando hay más de un par de parámetros o cuando hay varios parámetros opcionales. Básicamente, hace que las llamadas a métodos sean manifiestamente autodocumentadas.

Rico
fuente
3

No es una práctica común en Ruby usar un hash en lugar de parámetros formales.

Creo que esto se está confundiendo con el patrón común de pasar un hash como parámetro cuando el parámetro puede tomar varios valores, por ejemplo, establecer atributos de una ventana en un juego de herramientas GUI.

Si tiene varios argumentos para su método o función, entonces explíquelos explícitamente y páselos. Obtiene la ventaja de que el intérprete comprobará que ha pasado todos los argumentos.

No abuse de la función de idioma, sepa cuándo usarla y cuándo no.

Chris McCauley
fuente
1
Si necesita una cantidad variable de argumentos, solo use def method(*args), los hash no resuelven ese problema en particular
MBO
1
Gracias por señalar la posible confusión. Realmente estaba pensando en el caso que planteó el póster original donde el orden y el número de parámetros es variable.
Chris McCauley
1
Para agregar a ese punto, ese anti-patrón también se conoce como Contenedor Mágico
jtzero
3

La ventaja de utilizar un Hashparámetro as es que elimina la dependencia del número y orden de los argumentos.

En la práctica, esto significa que luego tendrá la flexibilidad de refactorizar / cambiar su método sin romper la compatibilidad con el código del cliente (y esto es muy bueno cuando se construyen bibliotecas porque en realidad no se puede cambiar el código del cliente).

(El "Diseño práctico orientado a objetos en Ruby" de Sandy Metz es un gran libro si está interesado en el diseño de software en Ruby)

Aldo 'xoen' Giambelluca
fuente
Si bien su afirmación es cierta, no creo que sea la mejor manera de mantener una biblioteca compatible. Puede lograr lo mismo simplemente agregando nuevos parámetros como opcionales. No rompen la compatibilidad y aún se benefician de la claridad y la auto-documentación.
Dragan Nikolic
@DraganNikolic También estoy de acuerdo con las cosas de sany metz: los beneficios de no tener que organizar una lista de parámetros manuales son enormes y hay otras formas de autodocumentar el código en el libro
Mirv - Matt
Otra cosa que muchos (¡incluyéndome a mí!) A menudo olvidan es que el orden de los parámetros también es una dependencia. Esto significa que solo usar parámetros opcionales significa que no puede cambiar el orden de los parámetros por cualquier motivo (¡pero generalmente no quiere muchos argumentos args de todos modos!)
Aldo 'xoen' Giambelluca
2

Es una buena práctica. No es necesario que piense en la firma del método y el orden de los argumentos. Otra ventaja es que puede omitir fácilmente los argumentos que no desea ingresar. Puede echar un vistazo al marco ExtJS, ya que utiliza este tipo de argumento pasando de forma extensiva.

Li0liQ
fuente
1

Es una compensación. Pierde algo de claridad (cómo sé qué parámetros pasar) y verifica (¿pasé la cantidad correcta de argumentos?) Y gana flexibilidad (el método puede usar parámetros predeterminados que no recibe, podemos implementar una nueva versión que toma más parámetros y no rompa ningún código existente)

Puede ver esta pregunta como parte de la discusión más amplia de tipo Fuerte / Débil. Vea el blog de Steve yegge aquí. He usado este estilo en C y C ++ en los casos en los que quería admitir el paso de argumentos bastante flexible. Podría decirse que un HTTP GET estándar, con algunos parámetros de consulta, es exactamente este estilo.

Si opta por la evaluación hash, yo diría que debe asegurarse de que sus pruebas sean realmente buenas, los problemas con nombres de parámetros mal escritos solo aparecerán en el tiempo de ejecución.

djna
fuente
1

Estoy seguro de que a nadie que usa lenguajes dinámicos le importa, pero piense en la penalización de rendimiento con la que se verá afectado su programa cuando comience a pasar hash a funciones.

El intérprete posiblemente ser lo suficientemente inteligente como para crear un objeto hash static const y sólo hacer referencia a ella por el puntero, si el código está utilizando un hash con todos los miembros que son literales del código fuente.

Pero si alguno de esos miembros es variable, el hash debe reconstruirse cada vez que se llama.

He hecho algunas optimizaciones de Perl y este tipo de cosas pueden notarse en los bucles de código internos.

Los parámetros de función funcionan mucho mejor.

Zan Lynx
fuente
0

En general, siempre deberíamos usar argumentos estándar, a menos que no sea posible. Usar opciones cuando no tiene que usarlas es una mala práctica. Los argumentos estándar son claros y auto-documentados (si se nombran correctamente).

Una (y quizás la única) razón para usar opciones es si la función recibe argumentos que no procesa sino que simplemente pasa a otra función.

Aquí hay un ejemplo que ilustra que:

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

En este caso, 'myfactory' ni siquiera conoce los argumentos requeridos por 'myobj1' o 'myobj2'. 'myfactory' simplemente pasa los argumentos a 'myobj1' y 'myobj2' y es su responsabilidad verificarlos y procesarlos.

Dragan Nikolic
fuente
0

Los hash son particularmente útiles para pasar múltiples argumentos opcionales. Utilizo hash, por ejemplo, para inicializar una clase cuyos argumentos son opcionales.

EJEMPLO

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
Leonardo Ostán
fuente