Siempre es posible ser más DRY
Que, cuando hablamos de programación (y yo lo hago bastante) no quiere decir que seas un seco, sino que no te repitas. Que, en este ámbito (en otros sería tema para otro post y probablemente otro autor
), es Algo Bueno (tm).
Quién no ha tenido que definir varios métodos que hacían casi lo mismo. El enfoque clásico sería la refactorización: un método que haga lo genérico, posiblemente aceptando algún parámetro para flexibilizarlo un poco, y que los métodos hagan sus particularidades, y deleguen en el primero lo que es genérico. Pero definir tooooodos esos métodos no deja de ser un coñazo (por más que sean cortos y simples). Jay Fields (si programas en Ruby y no lees su blog, ya lo estás añadiendo a tu lista; borra el mío aunque sea) nos enseña otra técnica, usando metaprogramación (programas que escriben programas, es tan sugerente, tan literario...).
El ejemplo (yo lo he simplificado porque yo soy más simple) está en Ruby pero supongo que será posible en muchos otros lenguajes. Al menos si molan tanto como Ruby ![]()
Primero vamos a crear el método que escriba nuestros métodos (ya que nosotros somos demasiado vagos para hacerlo nosotros mismos).
class Class def def_each(*methods, &block) methods.each do |method| define_method method do yield(method) end end end end
Como somos así de chulos, lo definimos en la clase Class, así que estará disponible para cualquier clase que definamos. Si no lo entiendes, vuelve a leerlo, y si sigues sin entenderlo échale un ojo a la documentación de define_method y yield ![]()
Pues bien, definamos una clase, y usemos nuestro método-que-escribe-métodos:
class Prueba def_each :uno, :dos, :tres do |method| method.to_s.size end end
Habéis leído bien: he definido tres métodos que devuelven el número de caracteres de su nombre:
prueba = Prueba.new puts prueba.uno [3] puts prueba.dos [3] puts prueba.tres [4]
Prometedme que lo usaréis para algo menos absurdo ![]()
Actualización: Si tuvísteis la curiosidad de probar el código, comprobaríais que funciona como se esperaba. Sin embargo, si me hicísteis caso y lo intentásteis emplear para algo más útil, os encontraríais con una pequeña dificultad, que hace que mi implementación de def_each sea, en realidad, inútil para cosas que no sean ejemplos triviales
. El problema está en el uso de yield, que hace que el código se evalúe en el contexto de la clase y no de la instancia, de modo que no se puede acceder a atributos o métodos de la instancia. Vaya gracia. Pero como en esta vida todo tiene arreglo, y programando en Ruby más aún, he aquí la implementación correcta:
class Class def def_each(*methods, &block) methods.each do |method| define_method method do instance_exec method, &block end end end end
Ojo, instance_exec es una novedad de Ruby 1.9, así que si usas una versión anterior (y si usas Rails es lo más probable), no funcionará. Sin problema tampoco, aquí (también cortesía de Jay) hay una implementación de instance_exec que funciona bien en 1.8.
Otra actualización: la mayoría podéis ignorar esta última advaertencia. Parece ser que Rails, en ese saco de sorpresas que es ActiveSupport, incluye su propia implementación de instance_exec.





mamuso dijo
Prometido! ya verás :)
5 Agosto 2007 | 08:49 PM