Muchas veces tenemos código ya hecho que está funcionando. También muchas veces necesitamos tocar ese código para que haga más cosas, para hacer más eficiente un algoritmo, más vistosa la salida del programa, porque tenemos otro proyecto que se parece, etc, etc.
También es normal que cuando queramos modificar el código, encontremos que el código ya hecho no es todo lo amigable que quisieramos. Introducir las modificaciones nos cuesta más de la cuenta porque hay métodos muy largos que no se entienden bien, hay pocos comentarios, hay trozos de código que nos sirven en parte, pero no del todo, clases muy grandes de las que nos sirven algunos métodos pero nos sobra el resto, etc, etc.
Una opción es apañarse con lo que se tiene, añadir la funcionalidad como podamos, copiar código, pegarlo en otro sitio y modificarlo para que se parezca a lo que queremos añadir, etc, etc. Esta opción tiene una pega, deja el código más "guarro" todavía de lo que estaba. La próxima vez que queramos hacerle algo, será peor.
La otra opción, la buena, es arreglar el código existente antes de añadirle la funcionalidad que queremos. Si necesitamos un trozo de código que está dentro de un método grande, partimos el método en dos, de forma que en un método tenemos lo que necesitamos y en otro el resto. Una vez hechos este tipo de arreglos, dejando el código funcionando igual que antes de hacerle los cambios, debería ser más fácil añadir la nueva funcionalidad.
Esta técnica de "arreglar" el código antes de abordar las modificaciones que realmente queremos meter es lo que se conoce como "refactoring" (rehacer código para los cristianos).
Lo ideal es hacer el refactoring sobre la marcha, según se va escribiendo código. La idea la explica perfectamente la metáfora de los dos sombreros. Según esta metáfora, un programador tiene a su disposición dos sombreros. Uno de ellos etiquetado "hacer código nuevo", y el otro con la etiqueta "arreglar código".
Cuando empieza a programar, se pone el sombrero de "hacer código nuevo". Se pone a programar hasta que tiene hecha alguna parte del programa y le hace una primera prueba, compilando y viendo que funciona. Deja esa parte del programa funcionando. Sigue con el mismo sombrero puesto y se prepara para seguir haciendo su programa. En ese momento ve un trozo de código que podría reaprovechar si estuviera separado en otra función o ve cualquier otra cosa que si estuviera hecha de otra forma, le facilitaría la tarea de seguir con su programa. O simplemente ve algo que no le convence cómo está hecho. En ese momento se cambia el sombrero. Se quita el de "hacer código nuevo" y se pone el de "arreglar código".
Ahora sólo está arreglando el código. No mete nada nuevo. Echa un rato cambiando código de sitio, haciendo métodos más pequeños, etc, etc. Al final deja el código funcionando exactamente igual que antes, pero hecho de otra manera que le facilita seguir con su trabajo. Nuevamente se cambia el sombrero por el de "hacer código nuevo" y sigue programando.
La idea es hacer código nuevo a ratos, arreglar código existente a ratos. Tener claramente qué se está haciendo en cada momento. Si añadimos código nuevo, NO arreglamos el existente. Si estamos arreglando el existente, NO añadimos funcionalidades nuevas. La idea también es arreglar el código con frecuencia, cada vez que veamos que algo no está todo lo bien hecho que debiera. Es decir, hacer refactoring sistemáticamente.
Cualquier programador con un poco de experiencia sabe que nunca se diseña bien el código a la primera, que nunca nos dicen al principio todo lo que tiene que hacer el código, que nuestros jefes, según vamos programando y van viendo el resultado van pidiendo cosas nuevas o midificaciones, etc.
El resultado de esto es que nuestro código, al principio, puede ser muy limpio y estar bien organizado, siguiendo un diseño más o menos claro. Pero según añadimos cosas y modificaciones, cada vez se va "liando" más, cada vez se entiende pero y cada vez nos cuesta más depurar errores.
Si vamos haciendo refactoring sistemáticamente cada vez que veamos código feo, el código se mantiene más elegante y más sencillo. En fases más avanzadas de nuestro programa, seguirá siendo un código legible y fácil de modificar o de añadirle cosas.
Aunque inicialmente parece una pérdida de tiempo arreglar el código, al final del mismo se gana dicho tiempo. Las modificaciones y añadido tardan menos y se pierde mucho menos tiempo en depurar y entender el código.
Una cosa importante del refactoring es que se parte de un código que más o menos funciona, se modifica y el código debe seguir funcionando igual que antes. Si hacemos refactoring con frecuencia, es importante tener algún medio de probar que el código sigue funcionando después de "arreglarlo".
Una opción es, después de arreglar el código, compilarlo, ejecutarlo y probar que más o menos sigue funcionado, especialemente en las partes de código que hemos tocado.
Otra opción algo más "profesional" es hacer unos pequeños programas de prueba. Estos programas (conocidos como test unitarios) deben instanciar la clase que vamos a tocar, llamar a sus métodos pasando determinados parámetros concretos y ver que devuelve el resultado adecuado. Una vez que tenemos el test, arreglamos el código de esa clase y luego pasamos el test. Si pasa el test, podemos tener cierta garantía de que todo sigue funcionando igual que antes.
Para que todo el proceso funcione bien y no haya demasiados problemas, es importante hacer un pequeño cambio y pasar el test, otro pequeño cambio y volver a pasar el test y así hasta que hayamos hecho el cambio que queramoas hacer. Nunca debemos embarcarnos en un cambio grande, pasarnos varias horas arreglando código sin pasar ningún tipo de prueba y al final hacer la prueba. Si la prueba no pasa, tardaremos también un buen tiempo en encontrar dónde está el fallo.
Tanto el refactoring como los test unitarios son pilares fundamentales de la programación extrema. En java se da soporte a este tipo de tests unitarios por medio de JUnit. La inmensa mayoría de los IDEs de desarrollo, como eclipse, tienen opciones de "refactoring" en las que señalamos un trozo de código y le decimos qué queremos hacer con él (cambiar nombre de variable, extraer el código en un método nuevo, mover un método de una clase hija a una clase padre, etc, etc). El IDE hace todo el cambio el solito y arregla en todas las demás clases lo que sea necesario.
Según muchas opiniones en muchos sitios, el libro por excelencia sobre refactoring es "refactoring" de Martin Fowler. La web de refactoring (creo que también de este señor) es http://www.refactoring.com.
El libro es realmente interesante, especialemente un capítulo en el que comenta qué cosas se deben arreglar en el código. La idea en ese capítulo es que debe arreglarse en el código cualquier cosa que "huela mal". Algunas de esas cosas son:
Anteriormente comenté que los cambios deben hacerse poco a poco, probando después de cada poco. Gran parte del libro de "refactoring" son "recetas" de cambios. Para cada posible cambio que queramos realizar (por ejemplo, partir un método en métodos más pequeños), nos dice qué mini-cambios debemos hacer y en qué orden. Por ejemplo, para partir un método grande en varios pequeños (esto es inventado, no lo que pone el libro):
En el libro habla de los comentarios de código como cosas que se deben refactorizar. El comentario no es una cosa que "huele mal", al revés, es como un perfume. El problema de los comentarios es que usan para tapar los malos olores.
Dicho de otra forma, si en un trozo de código o en un método nos vemos en la necesidad o creemos que es adecuado poner un comentario, es porque ese código no es lo suficientement claro. Si no es lo suficientemente claro, debe rehacerse para que lo sea. Por ejemplo, si yo tengo este código
a=33*b-Math.round(b+cerdoGordo);
pues para que se entienda le pongo un comentario del estilo
// calcula la cantidad
de grasa de un chorizo de mi pueblo
a=33*b-Math.round(b+cerdoGordo);
Según el libro, lo ideal es hacer un método con el nombre lo suficientemente claro como para que no necesite comentario. Por ejemplo
public double calculaCantidadGrasaChorizo (double b, double cerdoGordo)
{
return 33*b-Math.round(b+cerdoGordo);
}
y en donde hacíamos el cálculo simplemente ponemos
a=calculaCantidadGrasaChorizo (b, cerdoGordo);
y sobra el comentario. O se puede poner, pero es redundante.