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ículosEvitando estructuras condicionales para lograr código más legible y estable. Ejemplos en Ruby.
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:
-
-
# antes
-
mensage = if @nota.estado == :publicado
-
‘Nota publicada!’
-
elsif @nota.estado == :borrador
-
‘Nota guardada como borrador’
-
elsif @nota.estado == :rechazado
-
‘Tu nota ha sido rechazada’
-
else
-
‘Nota en moderacion’
-
end
-
… trato de pensar en una tabla de estados exclusivos, resueltos con un simple Hash.
-
-
mensaje = {
-
-
:publicado => ‘Nota publicada!’,
-
:borrador => ‘Nota guardada como borrador’,
-
:rechazado => ‘Tu nota ha sido rechazada’
-
-
}.fetch( @nota.estado, ‘Nota en moderacion’ )
-
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.
-
-
function obtenerMensaje( tipo ){
-
var mensajes = {
-
exito: ‘Operacion realizada con exito’,
-
error: ‘Hubo un error’,
-
invalido: ‘Por favor revisa la informacion’
-
};
-
-
return mensajes[tipo] || mensajes[‘exito’];
-
}
-
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.
-
-
<% if @contenido.is_a?(Articulo) %>
-
-
<% if @contenido.estado == :publicado %>
-
-
<h2>Tu articulo ha sido publicado, felicitaciones!</h2>
-
-
<% elsif @contenido.estado == :borrador %>
-
-
<h2 class="borrador">Tu articulo es un borrador</h2>
-
-
<% end %>
-
-
<% elsif @contenido.is_a?(Comentario) %>
-
-
<% if @contenido.estado == :publicado %>
-
-
<h3>Comentario publicado, gracias!</h3>
-
-
<% elsif @contenido.estado == :rechazado %>
-
-
<h3>Eres un maldito <strong>spammer</strong>!</h3>
-
-
<% end %>
-
-
<% end %>
-
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!
-
-
< %= render :partial => File.join( ‘contenidos’, @contenido.class.name.underscore, @contenido.estado )
-
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.
- Servicios:
- Comentarios RSS
- Menear!
- Del.icio.us

3/19/2008 at 12:26 pm
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.
4/5/2008 at 1:30 pm
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];
}
4/9/2008 at 6:52 am
@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 ) endEn Javascript se puede hacer algo similar. Si el ejemplo asume que las distintas clases tienen la misma interface, esto es basicamente polimorfismo.
4/9/2008 at 6:57 am
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 }4/17/2008 at 5:58 pm
Nice!
.. me gusta la retroalimentacion de experiencias
Saludos por Londres estimado, mientras yo aca en Buenos Aires ya pienso retornar a Chile