Incondicionalmente

Visita este artí­culo en http://www.estadobeta.com/2008/03/18/incondicionalmente/

Por Ismael en Desarrollo, Patrones de diseño, Ruby & Rails, artículos

Evitando estructuras condicionales para lograr código más legible y estable. Ejemplos en Ruby.

Mestinon For Sale Septilin No Prescription Buy Abana No Prescription Buy Online Avodart Buy Himcolin Online Hytrin For Sale Levitra No Prescription Buy Proscar No Prescription Buy Online Lopid Buy Zebeta Online Azulfidine For Sale Karela No Prescription Buy Clomid No Prescription Buy Online Loxitane Buy Actoplus Met Online Capoten For Sale Celebrex No Prescription Buy Retin-A No Prescription Buy Online Prednisone Buy Levitra Online Prednisone For Sale Tricor No Prescription Buy Tramadol No Prescription Buy Online Lamisil Buy Inderal Online

Sin contar al reggaeton y las berenjenas, no hay nada que deteste más que la lógica condicional.

Demasiadas veces me he topado con interminables secuencias de if / elsif / else, case / when (o switch / case, dependiendo del lenguaje) enredados entre bloques de código, que luego tienen más condiciones anidadas, unas negando a las otras, algunas olvidadas que sólo se cumplen un Domingo a las 3 am, 1 año después de escritas cuando nadie sabe muy bien que hacían ahí en primer lugar.

No me entiendan mal. Las estructuras condicionales son uno de los fundamentos de todo lenguaje, pero su abuso es también la puerta a todas las penas del Infierno Computín*.

Todo buen programador lo sabe: cuando esos if empiezan a aparecer entre tu código es hora de rediseñar tu aplicación (si digo eso una vez más en este blog tírenme tomates). Si tienes la fortuna de trabajar con OOP, ese rediseño pasa muy probablemente por utilizar polimorfismo, donde tienes varios objetos con el mismo comportamiento (interfaz) pero distintas implementaciones.

También vale atenerse a ciertas premisas: si una condición tiene más de un elemento (if x || y && (y + z) > 33 ...) estás efectuando una operación que probablemente debiera estar encapsulada en su propio método. Cuando la operación se escapa de la tarea específica que intentas solucionar (cuando tu método o función cumple más de una tarea), también es señal de que necesitas otro método o clase. Luego de un tiempo la repetición de esas premisas se transforman en reflejos y vas desarrollando el sentido estético que te hace arrugar la nariz cuando el código es “feo”.

Pero incluso cuando la escala no justifica rediseños drásticos, hay ciertas prácticas que ayudan a ordenar el código y evitar las ambigüedades de las condiciones. En Ruby, cuando tengo un bloque de condiciones que llegan a un resultado:

Code (ruby)
  1.  
  2. # antes
  3. mensage = if @nota.estado == :publicado
  4.   ‘Nota publicada!’
  5. elsif @nota.estado == :borrador
  6.   ‘Nota guardada como borrador’
  7. elsif @nota.estado == :rechazado
  8.   ‘Tu nota ha sido rechazada’
  9. else
  10.   ‘Nota en moderacion’
  11. end
  12.  

… trato de pensar en una tabla de estados exclusivos, resueltos con un simple Hash.

Code (ruby)
  1.  
  2. mensaje = {
  3.  
  4.   :publicado  => ‘Nota publicada!’,
  5.   :borrador   => ‘Nota guardada como borrador’,
  6.   :rechazado => ‘Tu nota ha sido rechazada’
  7.  
  8. }.fetch( @nota.estado, ‘Nota en moderacion’ )
  9.  

Hash#fetch nos entrega el valor correspondiente a la llave @nota.estado, o el segundo valor como defecto si esta no existe.

Este pequeño patrón no es exclusivo de Ruby. En Javascript suelo usar estructuras similares para eliminar ambigüedades del código.

Code (javascript)
  1.  
  2. function obtenerMensaje( tipo ){
  3.   var mensajes = {
  4.     exito:      ‘Operacion realizada con exito’,
  5.     error:     ‘Hubo un error’,
  6.     invalido: ‘Por favor revisa la informacion’
  7.   };
  8.  
  9.   return mensajes[tipo] || mensajes[‘exito’];
  10. }
  11.  

En Ruby on Rails, aunque la inteligente arquitectura del framework nos ahorra la mayor parte de conflictos, las vistas (plantillas html) suelen ser las más castigadas por nuestras condiciones. He visto (y si, hecho también) sábanas de HTML mezclado con bloques de Ruby que muestran este o ese otro fragmento según las más diversas condiciones. El ejemplo que sigue, aunque simplificado, es ya difícil de leer e incurre en la doble inprudencia de las condiciones anidades.

Code (html)
  1. <% if @contenido.is_a?(Articulo) %>
  2.   <% if @contenido.estado == :publicado %>
  3.     <h2>Tu articulo ha sido publicado, felicitaciones!</h2>
  4.   <% elsif @contenido.estado == :borrador %>
  5.     <h2 class="borrador">Tu articulo es un borrador</h2>
  6.   <% end %>
  7. <% elsif @contenido.is_a?(Comentario) %>
  8.   <% if @contenido.estado == :publicado %>
  9.     <h3>Comentario publicado, gracias!</h3>
  10.   <% elsif @contenido.estado == :rechazado %>
  11.     <h3>Eres un maldito <strong>spammer</strong>!</h3>
  12.   <% end %>
  13. <% end %>
  14.  

Pensandolo un poco, casos como este pueden ser perfectamente resueltos con el uso de partials y una simple convención de directorios. De hecho, no necesito condiciones en absoluto!

Code (ruby)
  1.  
  2. < %= render :partial => File.join( ‘contenidos’, @contenido.class.name.underscore, @contenido.estado )
  3.  

PArtial pattern Lo anterior buscará vistas parciales en el directorio “contenidos”, donde tenemos subdirectorios para cada clase de contenido y, dentro de cada uno, plantillas para cada estado. Por ejemplo “/contenidos/articulo/borrador”, o “/contenidos/comentario/publicado”. Por supuesto, la llamada a render puede ser movida a un helper para simplificar aún más las vistas o para reutilizar el patrón en otro lado.

Aunque la brevedad de estos ejemplos puede hacer menos aparentes las ventajas, en general pensar nuetra lógica en términos de estados excluyentes -en lugar de condiciones, que no necesariamente se excluyen entre sí- nos ayuda a estructurar mejor el total de la aplicación, evitar errores inesperados y en última instancia conseguir al menos una mejor habitación en el Infierno Computín.

El en Infierno Computín hay sólo un modem de 14kbps que tienes que compartir con todos los penitentes en un PC con Windows 95. En el rincón que me espera a mi te despiertan todos los días con Daddy Yankee a todo volumen y un plato de berenjenas fritas.

5 comentarios para “Incondicionalmente”

  1. Gravatarvladimir Dice:

    al menos en JavaScript, hay que tener cuidado “ordenar” el código de formas distintas, sobre todo con lo que menciona el artículo: condiciones anidadas (o enredadas).

    ya nos lo cuentan los chicos de IEBlog, en donde un SWITCH “cuesta” mucho más que un (o varios) IF. esto toma relevancia en el caso de JS, ya que el código se ejucuta en el cliente, pudiendo demorar demasiado la aplicación.

  2. GravatarFabian Ramírez Dice:

    Excelente tu propuesta para evitar esas tediosas condiciones.

    Lo que me pregunto si podrian esas condiciones devolver objetos o funciones.

    Quizas podría ser algo asi?

    function obtenerFunciones( tipo ){

    var funciones = {
    exito: this.exito(),
    error: this.error(),
    invalido: this.invalido()
    };

    return funciones[tipo];
    }

  3. GravatarIsmael Dice:

    @fabian: de que se puede, se puede, y tiene sentido si los valores que quieres retornar son en si producto de operaciones complejas. Tu ejemplo no esta tan lejos de un tipico Factory que retorna subclases. En Ruby:

    class Factory( tipo, parametros  )
      clases = {
        :primero  => PrimeraClase,
        :segundo => SegundaClase,
        :tercero   => TerceraClase
      }
      cl = clases.fetch( tipo, DefaultClase ) # retorna DefaultClase si tipo no existe
      return cl.new( parametros )
    end
    

    En Javascript se puede hacer algo similar. Si el ejemplo asume que las distintas clases tienen la misma interface, esto es basicamente polimorfismo.

  4. GravatarIsmael Dice:

    Para extender tu ejemplo en Javascript, se puede delegar la ejecucion de las funciones. En el clasico caso de un validador de formularios:

    function validar( tipo, valor ){
    
      var validadores = {
        email: Validadores.email, // sin parentesis!
        not_empty: Validadores.notEmpty,
        alphanumeric: Validadores.alphanumeric,
        rut: Validadores.rut
      };
      var validador = validadores[tipo] || Validadores.default;
    
      return validador( valor ); // lo ejecutamos solo al final
    }
    
  5. GravatarFabian Ramírez Dice:

    Nice! :) .. me gusta la retroalimentacion de experiencias

    Saludos por Londres estimado, mientras yo aca en Buenos Aires ya pienso retornar a Chile :)

Deja un comentario

XHTML: puedes usar estas etiquetas: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>