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?
ruby
oop
coding-style
rmaruszewski
fuente
fuente
{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)Respuestas:
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.
fuente
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.
fuente
{…}
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 .Yo diría que si eres:
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
@option
etiqueta 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
fuente
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.
fuente
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.
fuente
def method(*args)
, los hash no resuelven ese problema en particularLa ventaja de utilizar un
Hash
pará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)
fuente
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.
fuente
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.
fuente
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.
fuente
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.
fuente
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
fuente