2009-12-18 10 views
71

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 los parámetros concretos del método. Por ejemplo, en lugar de declarar un método con parámetros y decir que es como esto:Pasar hashes en lugar de los parámetros del método

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

que puede hacerlo de esta manera:

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

me gustaría saber su opinión sobre el tema. ¿Es una buena o una mala práctica, deberíamos hacerlo o no? ¿En qué situación es válida esta práctica, y qué situación puede ser peligrosa?

+2

+1, en efecto, una buena pregunta –

+10

Una pequeña nota: '{width => 400, altura => 50, show_border => false}' no es sintaxis rubí 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) – henrikhodne

Respuesta

24

Ambos enfoques tienen sus propias ventajas y desventajas, cuando utiliza 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 parámetros pseudo-nombrados creados al usar un opciones hash.

Mi regla general es si tiene muchos argumentos para un método (más de 3 o 4) o muchos argumentos opcionales, entonces 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 describe los posibles argumentos.

+1

Ahora, si tuviera un hash de opciones, ¿podría ponerlo? todo el hash en, o tengo que pasar las claves => valores individualmente? – Pred

+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 deberían usarse solo si esa es la única forma de resolver un problema. –

1

Es una buena práctica. No necesita pensar 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 está utilizando este tipo de argumento pasando extensamente.

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 una serie de parámetros opcionales. Básicamente, hace que las llamadas a métodos sean manifiestamente autodocumentadas.

3

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

creo este se confunde con el patrón común de pasar un hash como un parámetro cuando el parámetro puede tomar un número de valores, por ejemplo, establecer atributos de una ventana en un kit de herramientas GUI.

Si tiene una cantidad de argumentos para su método o función, explíquelos explícitamente y páselos. Obtiene el beneficio de que el intérprete verifique que haya pasado todos los argumentos.

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

+1

Si necesita un número variable de argumentos, simplemente use 'def method (* args)', los hashes no resuelven ese problema en particular – MBO

+1

Gracias por señalar la posible confusión. Realmente estaba pensando en el caso del cartel original donde el orden y la cantidad de parámetros es variable. –

+1

Para agregar a ese punto, ese antipatrón es también conocido como [Magic Container] (http://c2.com/cgi/wiki?MagicContainer) – jtzero

34

Ruby tiene parámetros de hash implícitas, por lo que también podría escribir

def my_method(options = {}) 

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

y con Ruby 1.9 y nueva sintaxis de 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.

1

Es una compensación. Pierdes algo de claridad (¿cómo sé qué parametros pasar?) Y verificando (¿pasé el número correcto de argumentos?) Y obtengo flexibilidad (el método puede parametrizar por defecto que no recibe, podemos implementar una nueva versión que tome más params y no se rompe ningún código existente)

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

Si elige el hash, diría que debe asegurarse de que las pruebas sean realmente buenas, los problemas con los nombres de los parámetros mal escritos solo se mostrarán en tiempo de ejecución.

7

Yo diría que si usted es ya sea:

  1. Tener más de 6 Parámetros de métodos
  2. Pasando opciones que han requerido alguna, algunos opcionales y algunos con valores por defecto

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 determinar qué opciones toma un método, eso solo significa que el código está poco documentado. Con YARD, puede utilizar la etiqueta @option 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 este ejemplo específico no hay tales parámetros pocas y simples, así que creo que me gustaría ir 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 
1

I Estoy seguro de que a nadie que use lenguajes dinámicos le importe, pero piense en la penalización de rendimiento que tendrá su programa cuando empiece a pasar hash a las funciones.

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

Pero si alguno de esos miembros son variables, el hash debe reconstruirse cada vez que se invoca.

Realicé algunas optimizaciones de Perl y este tipo de cosas pueden notarse en los bucles de código interno.

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

2

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

En la práctica, esto significa que más adelante 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 crean bibliotecas porque no se puede cambiar el código del cliente) .

(de arena Metz "Practical Object-Oriented Design in Ruby" es un gran libro si está interesado en el diseño de software en Ruby)

+0

Si bien su declaració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. –

+0

@DraganNikolic Estoy a bordo con las cosas de sany metz también - los beneficios de no tener que organizar una lista de parámetros manuales son enormes y hay otras maneras de auto-documentar el código presentado en el libro – Mirv

+0

Otra cosa muchas (incluido yo) !) a menudo se olvida que el orden de los parámetros también es una dependencia. Esto significa que el uso de parámetros opcionales significa que no puede cambiar el orden de los params por el motivo que sea (¡pero de todos modos no quiere muchos argumentos de args!) –

0

En general siempre hay que utilizar argumentos estándar, a menos que no es posible. Usar las opciones cuando no tienes que usarlas es una mala práctica. Los argumentos estándar son claros y auto-documentados (si se nombran adecuadamente).

Una (y tal vez la única) razón para usar opciones es si la función recibe argumentos que no se procesan, sino que simplemente pasan a otra función.

Aquí es 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 es consciente de los argumentos requeridos por el 'myobj1' o 'myobj2'. 'myfactory' simplemente pasa los argumentos a 'myobj1' y 'myobj2' y es su responsabilidad verificarlos y procesarlos.

0

Los valores hash son particularmente útiles para pasar múltiples argumentos opcionales. Yo uso 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 
Cuestiones relacionadas