REDcientifica

Refactorización de código
6.642 visitas desde el 09/10/2011
Manu Herrán
Universidad de Deusto

Informática, Inteligencia Artificial, Programación

Artículo publicado en M2C-Consulting en Septiembre de 2011.
www.m2c.es


En el desarrollo de proyectos informáticos es habitual encontrar situaciones en las que un cambio aparentemente simple conlleva un gran coste de implementación. Y al contrario: en ocasiones es posible dotar al sistema de una nueva y gran funcionalidad con un esfuerzo muy pequeño.


¿Quieres saber más?
Regístrate para leer más artículos como este

¿Por qué ocurre esto? ¿Bajo que condiciones se da una u otra situación? ¿Que necesitamos para que en nuestro sistema sea posible añadir nuevas funcionalidades con un coste mínimo? La respuesta a estas preguntas está relacionada con una actividad omnipresente pero de nombre poco conocido: La refactorización del código.

Refactorizar consiste en modificar y re-escribir código informático de manera que siga realizando la misma función externa, pero con algún tipo de ventaja en cuanto a su estructura interna. Los posibles objetivos de la refactorización son muchos: obtener un código más reutilizable, más fácil de entender, más compacto, o más eficiente. Estos objetivos están relacionados con la calidad del código (cómo lo hace) y no con su funcionalidad (qué hace).

Todos los programadores refactorizan código, aunque la mayoría de ellos desconoce el término. Dejando a un lado la forma en la que llamamos a las cosas, lo importante es ser conscientes de la necesidad de adaptar el código para facilitar su evolución y la incorporación de nuevas funcionalidades. Y por tanto, añadir esta tarea en los planes.

Llegado el momento de incluir la tarea en el plan, el término tiene su cierta utilidad: "refactorización" puede ser mucho más glamuroso que un ambiguo "mejorar el código". En cualquier caso, es mejor hablar de adaptar el código (para permitir la incorporación de nueva funcionalidad), más que de mejorarlo.



¿Por que? Porque si hablamos de "mejorar" podemos dar a entender que aquello no estaba bien antes, sin ser ésto cierto. La estructura óptima de un fragmento de código depende de su funcionalidad externa y de su contexto. Cuando la funcionalidad se amplía o el contexto cambia, es lógico que la estructura interna también evolucione.

Imaginemos que tenemos una función que realiza ciertas tareas y recibe tres parámetros de entrada. Añadir nuevos parámetros cambiando todas las llamadas a esa función en el resto del código, sólo tiene sentido si los nuevos parámetros van a ser realmente utilizados en algún momento. Es natural que a medida que los requisitos evolucionan, también lo haga el código que ya estaba funcionando. El código era óptimo antes, sin los parámetros extra, y lo es ahora, con los nuevos parámetros. El código no ha mejorado, simplemente lo estamos "adaptando" o "evolucionando". Y para evolucionarlo, es necesario adaptar antes su estructura, es decir, "refactorizarlo".

Supongamos que la función original es llamada desde 27 lugares diferentes del código. Si más adelante queremos dotar al sistema de una nueva funcionalidad similar a la de esta función primera, podemos optar por construir una segunda función totalmente independiente, aunque ambas sean parecidas, o modificar la existente, y tal vez añadir mas parámetros de entrada (digamos que otros dos) y/o cambiar la forma en la que se especifican los parámetros de entrada (por ejemplo, un tipo binario se convierte ahora en numérico). Todo esto supone modificar no sólo esa función, sino sus 27 llamadas, y lo que es peor, probar posteriormente que las 27 llamadas funcionan correctamente.

Este ejemplo ilustra además hasta que punto es importante para el programador tener la mejor idea posible del uso futuro y evolución de los componentes que desarrolla. El programador con una correcta visión, construirá inicialmente la función con cinco parámetros y no con tres, aunque por ahora sólo se usen tres, y uno de ellos, aún siendo binario (0, 1) se especificará como numérico, para poder representar en el futuro otras posibilidades (0, 1, 2...). El coste de hacer esta definición extra inicial es mínimo. El coste de su modificación posterior, no.

Muy bien, pero no podemos cambiar el pasado. Tenemos una función con tres parámetros y necesitamos la nueva funcionalidad. ¿Que hacemos? Si decidimos implementar una segunda función totalmente independiente aún siendo muy similar a la primera, estaremos ganando en velocidad de implementación de funcionalidad, pero perdiendo sencillez, claridad, eficiencia y capacidad futura para hacer nuevos cambios. En el caso contrario, podemos optar por refactorizar la primera función, esto es, añadir y modificar los parámetros y las llamadas, facilitando la evolución futura del sistema, a pesar del coste que esto conlleva.

En mi opinión, la sostenibilidad del mantenimiento de las aplicaciones a largo plazo y la clave del éxito de los grandes proyectos de desarrollo, se basa fundamentalmente en este asunto tan "simple". Por eso es importantísimo que los programadores tengan una idea del uso futuro de lo que desarrollan, y que exista un clima de confianza y comunicación entre gestores y desarrolladores.

¿Cuándo refactorizar?

Existen enfoques metodológicos que ponen gran énfasis en la refactorización. Según algunos autores, debemos refactorizar siempre, en cada oportunidad, sin descanso. En general, podemos decir que es un enfoque adecuado. ¿Por qué? Veámoslo desde una cierta perspectiva:

Al igual que en tantas otras actividades, en el desarrollo de software se busca un equilibrio entre dos extremos opuestos. Por una parte queremos dotar al sistema de nueva funcionalidad lo antes posible. Por otra parte, deseamos facilitar el mantenimiento del código existente, así como la incorporación de nueva funcionalidad en el futuro.

Pensemos en algunos casos extremos. En uno de ellos, imaginemos un grupo de programadores desarrollando aplicaciones de entretenimiento. El equipo trabaja bajo la siguiente premisa: cada semana se debe generar un juego diferente, que sólo estará activo durante ese tiempo. Una vez finalizado el periodo, el juego deja de estar disponible para los usuarios.

En el otro extremo, pensemos en el desarrollo de un sistema gestor de contenidos (CMS: Content Management System) para varias publicaciones. Se espera que el CMS esté activo durante al menos 10 años, y no se ponen límites a su alcance: La intención es incluir continuamente nuevas funcionalidades en el sistema: nuevos formatos, plantillas, canales, publicaciónes, interacción con el usuario etc.

Seguramente, ambos equipos "reutilizarán" código, pero lo harán de forma totalmente diferente. Mientras que el primer equipo dispondrá de gran cantidad de código "muerto" que servirá como ejemplo para copiar y pegar en los nuevos desarrollos, casi todo el código que genere el segundo equipo se deberá mantener "vivo" y en perfectas condiciones a medida que se incorporen nuevas funcionalidades al sistema.

El el primer caso, los tiempos de desarrollo serán más predecibles, y dependerán de la complejidad de los nuevos juegos, y de lo mucho o poco que se parezcan a los juegos anteriores. En el segundo caso en cambio, si sólo conocemos el nuevo comportamiento externo deseado para la aplicación CMS, será muy difícil estimar el coste que supondrá añadir esa nueva funcionalidad: el coste dependerá totalmente de la forma en la que se haya desarrollado el sistema. Especialmente, dependerá de si el código es reutilizable, compacto y fácil de entender: Dependerá de si se ha refactorizado suficientemente.

Este segundo caso es mucho más común que el primero, y en general, cuanto mayor es un proyecto, mayores necesidades de refactorización conlleva. De hecho la necesidad de refactorización es directamente proporcional a la complejidad y a la necesidad de mantenimiento, e inversamente proporcional a la calidad del código, en el sentido de su adecuación a las condiciones actuales.

Podríamos representarlo con una fórmula. En una primera aproximación, podemos hacer equivalentes complejidad y tamaño. Por otra parte, la "necesidad de mantenimiento" está relacionada con el tiempo que se espera que el sistema se encuentre activo, y con el número de cambios esperados en ese tiempo. Entonces tenemos que:

[Necesidad de Refactorización] = [Tamaño del sistema] * [Esperanza de vida del sistema] * [Número de cambios previstos] / [Calidad actual]

Seguramente el "peso" de los factores sobre el resultado sea diferente al aquí representado, especialmente porque es más correcto decir que la complejidad crece exponencialmente con el tamaño. Además, no se trata tanto del número de cambios previstos, como de su complejidad. Por tanto una segunda aproximación puede ser de esta forma:

[Necesidad de Refactorización] = [Complejidad actual] * [Complejidad futura] * [Esperanza de vida del sistema] / [Calidad actual]

O mejor así:

[Necesidad de Refactorización] = K1 ^ [Tamaño actual] * K2 ^ [Tamaño de los cambios] * [Esperanza de vida del sistema] / [Calidad actual]

Queda pendiente, y fuera de la ambición de este artículo, establecer experimentalmente medidas para estas variables y ajustar la fórmula adecuadamente.

En cualquier caso, es evidente que es un duro trabajo tener bajo control la complejidad creciente de un sistema de cierto tamaño, que evoluciona y que pretende mantenerse vivo durante un largo tiempo. Gran parte de este esfuerzo consiste en la refactorización.

Pero la "dureza" de esta tarea no se debe a su propia complejidad (de hecho, refactorizar es una de las tareas más simples y mecánicas para un programador). La "dureza" de la tarea es debida a que habitualmente se infravalora su importancia. Desafortunadamente, la refactorización tiene difícil "visibilidad" en el proyecto, ya que por definición, no afecta al comportamiento del sistema, y sus beneficios no son obvios para quienes no están familiarizados con su estructura interna.

La refactorización tiene por tanto un doble inconveniente: puede parecer aburrida para el programador, y puede parecer inútil para el jefe de proyecto. Pero no tiene por qué ser aburrida, ya que se puede automatizar y convertirse en una funcionalidad muy interesante por sí misma. Y por supuesto que no es inútil, salvo que busquemos resultados a cortísimo plazo. En general, es fundamental para mantener al sistema vivo. Los buenos programadores lo saben y es por ello que refactorizan "siempre, en cada oportunidad, sin descanso", y si hace falta, "a escondidas" del plan. Salvo en contadas excepciones (sistemas pequeños, efímeros o apenas sin cambios), en todos los demás casos, la refactorización es necesaria e imprescindible.

La refactorización, metáfora del sueño

Existe un curioso paralelismo entre la refactorización en los sistemas artificiales, y los procesos relacionados con el dormir y el soñar en los seres vivos. El hecho de dormir y soñar no parece representar una ventaja obvia, a no ser que sea necesaria una suerte de proceso de "reorganización" mental, no sólo de la información recibida, sino también de los mecanismos para procesarla, para poder seguir aplicando los procesos cognitivos con éxito. Eso parece ocurrir tanto en unos como en otros sistemas complejos.

Tanto los seres vivos como las máquinas inteligentes, sueñan (o refactorizan).

Enlaces:

Agilizar.es - Juan Gutiérrez
http://agilizar.es/blog/category/refactorizacion/

Refactoring - Martin Fowler
http://refactoring.com/

Refactorización en Código Fortran Heredado - Mariano Méndez et al.
http://jeff.over.bz/papers/2010/cacic2010.pdf



 ¿Te ha gustado este artículo?
Colabora con
REDcientifica
desde 1 euro


  



Comentarios



2011-10-09 23:08:59
exelente , gracias.






2011-11-15 15:33:29
Muy buen artículo. Lo compartiré.



Les animo también a que den soporte a feeds (rss/atom) y Google+1. Así es más fácil seguirlo y promocionarlo.






2011-11-15 16:52:54
Gracias. He añadido un párrafo final que quedó en el tintero.

"La refactorización, metáfora del sueño". Espero que les guste.



Manuel.




2011-11-15 17:44:07
Gracias por la sugerencia Google+ y RSS. ¿Podría enviarnos por este medio o por otro su usuario de Google+ para ponernos en contacto? Gracias.



Admin.






2011-11-16 12:11:36
Claro, mi perfil en google es

https://plus.google.com/104718757519673756328/about
(soy el que escribió el 2011-11-15 16:52:54)




   

Enviar un comentario anónimo
Los comentarios se publican de forma anónima, pero puedes indicar tu nombre o nick en el propio comentario. No se permiten caracteres html, pero puedes poner la URL en una línea independiente de la forma http://www.ejemplo.com/ y la convertiremos en un link.

¿Eres humano? 

REDcientifica

Ciencia, Tecnología y Pensamiento

¿Has olvidado tu contraseña?
Usuario o Email
Clave


   


Últimos comentarios
» http://www.redfilosofica.org/como_pri...
» http://www.redfilosofica.org/como_pri...
» http://www.redfilosofica.org/como_pri...
» Muy bonito, me ha gustado mucho la reflexión, no obstante recomiendo no embriagarse de fantasías...
» Lo siento, no hemos podido migrar automáticamente todos los artículos al nuevo sistema de redcie...


Temas
Antropología (12)
Aprendizaje (18)
Arte (1)
Astronáutica (1)
Biografía (1)
Biología (11)
Cerebro (12)
Ciencia (28)
Ciencia-ficción (2)
Computación Evolutiva (1)
Consultoría (5)
Cosmología (4)
Cuántica (6)
Ecología (5)
Economía (23)
Educación (24)
Ética (13)
Evolución (8)
Filosofía (68)
Física (13)
Futuro Vegano (2)
Genética (4)
Gnoseología (7)
Historia (3)
Informática (8)
Ingeniería (3)
Inteligencia Artificial (10)
Lingüística (2)
literatura (1)
Lógica (3)
Marketing (8)
Matemáticas (6)
Medicina (24)
Método Científico (9)
Negocios (2)
Neurociencia (3)
Política (12)
Programación (4)
Psicología (15)
Química (1)
REDcientifica (6)
Redes Neuronales (3)
Robótica (5)
Sensocentrismo (1)
Sistemas Complejos (5)
Sociología (26)
Subjetividad (26)
Tecnología (15)
Visión Artificial (1)

 redcientifica.org en Twitterredcientifica.org en Facebookredcientifica.org en Google+ 

redcientifica.org en LinkedIn



CSS Validator