Refactorización de código Manuel de la Herrán Gascón Universidad de Deusto Informática, Inteligencia Artificial, Sistemas Complejos, 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.
Por ejemplo, añadir tres nuevos parámetros de entrada y duplicar el tamaño de una función, 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. 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 tres parámetros, y lo es ahora, con los nuevos parámetros. El código no ha mejorado, simplemente lo estamos "adaptando" o "evolucionando", es decir, estamos "refactorizando".
¿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.
Sin embargo 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 las contadas excepciones en las que el resultado de la fórmula anterior se aproxima a cero (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 | |
| |
|
Comentarios2011-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.
|