martes, abril 08, 2008

Parentheses are evil

A veces la legibilidad del código es subestimada, sin embargo es tal vez de las cosas mas importantes.

Últimamente he estado trabajando mucho en F# y me he topado por accidente con pedazos código que me gustan no por lo elegantes sino por su legibilidad.

Digamos que tenemos un código en un pseudo-Java-C#


   35 public static IElementsContainer logout(IElementsContainer ie){

   36   IElementsContainer logout = Helpers.elementById("LoginStatus1", ie);

   37   if(logout.Exists){

   38     logout.click();

   39   }

   40   return ie;     

   41 }



Y lo comparamos con esto:


   38 let logout ie =

   39     let logout = elementById "LoginStatus1" ie

   40     if logout.Exists then click logout

   41     ie



Me voy a hacer el bobo con el ahorro en caracteres. Pregunto... ¿Soy el único al que le parece la linea 40 del segundo ejemplo hermosa?

Esta linea es posible solo porque en F# como en muchos lenguajes funcionales, se utilizan funciones a la Curry. Se podría reproducir el efecto en lenguajes imperativos como Ruby dado que los paréntesis resultan opcionales. El ejemplo anterior es sencillo, pero no es nada sorprendente.

Ahora un caso típico, Digamos que tenemos un objeto que posee un estado y que debemos realizar una serie de operaciones sobre este que cambian su estado. Y supongamos que el orden es importante. Las funciones retornan una referencia al objeto modificado.

Esta sería la forma cerda:


   40 

   41 public IElementContainer login(String user, String passwd,

   42                                IElementContainer ie){

   43 

   44     return clickById("Login1_LoginButton",

   45             typeById("Login1_Password", passwd ,

   46             typeById("Login1_UserName", user ,

   47               logout(ie)));

   48 

   49 }



Legibilidad Nula. Pero digamos que el que escribió eso se sintió bien porque "se ahorro los punto y comas":

Ahora hagamoslo mas "imperativo".


   41 public IElementContainer login(String user, String passwd,

   42                                IElementContainer ie){

   43     ie.logout(ie);

   44     ie.typeById("Login1_UserName",user);

   45     ie.typeById("Login1_Password", passwd); 

   46     ie.clickById("Login1_LoginButton");

   47     return ie;

   48 }



Digamos que legible. Y F#? podemos implementar algo parecido a la primera solución pero que se deja leer en el mismo sentido que ocurren los sucesos.


   41     let login user passwd ie =

   42         logout ie 

   43         |> typeById "Login1_UserName" user

   44         |> typeById "Login1_Password" passwd 

   45         |> clickById "Login1_LoginButton"



Él operador |> es como el pipe de shell. El resultado se lo pasa a la funcion de la derecha.

<3


PD: Los ejemplos son sacados de mu ultima experiencia trabajando con F#, Watin y NUnit. WatiN es un API para Testing Funcional (Funcional de Funcionalidad no de FP). Pese a la poca documentación de WatiN lo recomiendo en forma enorme.

2 comentarios:

Federico Builes dijo...

Como decís, la línea 40:
40 if logout.Exists then click logout

Se puede escribir en Ruby como:

click logout if logout.Exists

Pero en este caso no tiene nada que ver con currificación en sí, la justificación viene desde la gramática:

Si click recibe un parametro entonces el primer token no delimitador despues de click será su parametro.

diegoeche dijo...

De hecho se puede hacer exactamente igual:

if cond then click logout end

Y es cierto... es la gramática. En ruby son opcionales los paréntesis (a veces sacará warning). Eso lo digo en el post