miércoles, 29 de abril de 2009

Refactoring y Cómo corregir sin Destruir

Muchas veces me toca, en conjunto con mi equipo, resolver problemas, o las bien llamadas Incidencias, que no son detectados durante la etapa de QA y que impactan a clientes que están en producción. Típicamente, las correcciones de este tipo, conocidas como Parches y/o Fixes, involucran una intervención de código que está en producción, sin mucho tiempo para grandes especificaciones y, por último, con un cliente ansioso por una solución.

En estos escenarios, es necesario ejecutar un proceso como el siguiente:

  • Identificar el Error
  • Replicar el Error
  • Analizar e Identificar la Causa
  • Corregir el Error
  • Probar y Certificar la Solución
  • Publicar

Dependiendo de la complejidad del problema, algunas etapas se pueden omitir, sin embargo, típicamente, la etapa de corrección siempre está presente (a menos que el problema sea el usuario y/o esté entre el teclado y la silla).

Haciendo una analogía, esto es casi como lo que realiza un cirujano, es decir, abrir un cuerpo vivo (el software), tomar una decisión (analizar e identificar la causa), extraer, implantar y/o limpiar (modificar el código fuente), cerrar el cuerpo (compilar y probar) y dar de alta (publicar). Por suerte en el caso del software, casi nunca, está la vida de alguien de por medio.

El problema es que hacer una intervención para corregir un código sin mucho tiempo para probar, ni hacer pruebas de regresión, etc., no es una tarea fácil. La intervención debe asegurar que no hay efectos colaterales y, aunque no es infalible, hay algunas guías para realizar esto. Esto es lo que se conoce como Refactoring que, según la definición, es una intervención en un software para mejorar su legibilidad y/o su estructura sin modificar el resultado. Veamos algunos ejemplos.

Ejemplo 1. Funciones y Redefiniciones

Supongamos una función como la siguiente para validar si un string es un número entero:

int validaEntero( String sText ) {
 try
 {
  return Integer.parseInt( sText );
 }
 catch ( Exception ex )
 {
  return 0;
 }
}

En la declaración anterior se asume que el 0 es un valor de error y, supongamos, que se detecta un escenario en donde el valor 0 es válido. Claramente la función no permite detectar directamente si viene un 0 y/o un valor erróneo en la entrada. El primer impulso para corregir esto es, o debiera ser, agregar la posibilidad de incorporar un valor por default, por ejemplo:

int validaEntero( String sText, int iDefault ) {
 try
 {
  return Integer.parseInt( sText );
 }
 catch ( Exception ex )
 {
  return iDefault;
 }
}

Un programador inexperto, probablemente modificaría la misma función ya existente produciendo un desastre con otras partes del código que utilicen la función sin el valor por default. Un programador con algo más de experiencia, probablemente agregaría la función anterior generando dos funciones prácticamente idénticas en el código pero con firmas distintas. Ahora bien, un programador consciente de un proceso de refactoring reutilizaría la nueva función en la primera para mantener de alguna manera la funcionalidad concentrada como sigue:

int validaEntero( String sText ) {
 return validaEntero( sText, 0 ); // 0 Valor por default
}

int validaEntero( String sText, int iDefault ) {
 try
 {
  return Integer.parseInt( sText );
 }
 catch ( Exception ex )
 {
  return iDefault;
 }
}

Ejemplo 2. Páginas Web y Parámetros

Supongamos una página web que recibe un parámetro, como resultado de un POST HTTP, para eliminar una actividad como sigue (el parámetro como GET sólo para facilitar la descrpción):

http://X.X.X.X/eliminaActividad.jsp?id=9820

Esta página es utilizada por varias páginas de un sitio web para realizar esta acción (incluso, con AJAX, por ejemplo). En un contexto determinado, un programador decide que va a reutilizar la página para permitir eliminar una lista de actividades y, apresuradamente, decide modificar la página asumiendo que la entrada ahora va a ser como sigue:

http://X.X.X.X/eliminaActividad.jsp?id=9820,7645,7282

Si esta corrección se realiza mal (ver Inducción), lo más probable es que la página ya no funcione correctamente cuando se envíe un único Id de actividad. El proceso de intervención correcto tiene dos posibles soluciones:

  • Agregar un parámetro nuevo para proveer la lista de actividades, por ejemplo, idl (id List) y, según el parámetro provisto, realizar la acción correspondiente.
  • Procesar el parámetro id y determinar si corresponde o no a una lista de id´s de actividad.

Ejemplo 3. WebServices y Publicación de Métodos

Supongamos un WebService determinado que recibe dos parámetros a y b y que retorna la suma de a y b (nada muy complejo obviamente). Al igual que el ejemplo 1, se determina que se debe agregar un tercer parámetro para indicar si los parámetros a y b son números y/o representaciones hexadecimales de los mismos. En este caso, la inspección de código no permite dimensionar las consecuencias de esto porque no necesariamente se tiene control sobre los sistemas que estén integrados con este servicio "fundamental".

La secuencia de solución es similar a la indicada en el Ejemplo 1 en donde, básicamente, se debe ampliar la funcionalidad existente de manera de agregar un comportamiento nuevo.

El gran desafío entonces de este tipo de correcciones es lograr resolver el problema sin provocar efectos secundarios y esto, desde mi punto de vista, es un hábito que se debe aprender y practicar.

Hay un excelente artículo de con varias guías y cómo mejorar un código existente en Java en la revista JavaWorld

No hay comentarios.: