refactoring legacy-code

refactoring - Código de legado pesadilla



legacy-code (13)

He heredado un proyecto donde los diagramas de clase se parecen mucho a una tela de araña en un plato de espaguetis. He escrito alrededor de 300 pruebas de unidad en los últimos dos meses para ofrecerme una red de seguridad que cubra el ejecutable principal.

Tengo mi biblioteca de libros de desarrollo ágil al alcance en cualquier momento:

  • Trabajando Efectivamente con el Código Legado
  • Refactorización
  • Código Completo
  • Principios ágiles patrones y prácticas en C #
  • etc.

El problema es que todo lo que toco parece romper algo más. Las clases de UI tienen lógica empresarial y código de base de datos mezclados. Existen dependencias mutuas entre varias clases. Hay un par de clases de dios que se rompen cada vez que cambio alguna de las otras clases. También hay una clase de utilidad / utilidad mutante con aproximadamente la mitad de los métodos de instancia y la mitad de los métodos estáticos (aunque, irónicamente, los métodos estáticos se basan en la instancia y los métodos de instancia no).

Mis predecesores incluso pensaron que sería inteligente usar todos los conjuntos de datos al revés. Cada actualización de la base de datos se envía directamente al servidor db como parámetros en un procedimiento almacenado, luego los conjuntos de datos se actualizan manualmente para que la interfaz de usuario muestre los cambios más recientes.

A veces me siento tentado a pensar que utilizaron algún tipo de ofuscación débil para la seguridad laboral o como una despedida final antes de entregar el código.

¿Hay buenos recursos para desenredar este lío? Los libros que tengo son útiles, pero solo parecen cubrir la mitad de los escenarios en los que me estoy topando.


Buena suerte, esa es la parte difícil de ser un desarrollador.

Creo que su enfoque es bueno, pero necesita enfocarse en la entrega de valor comercial (la cantidad de pruebas unitarias no es una medida del valor comercial, pero puede darle una indicación de si está en el buen camino). Es importante haber identificado los comportamientos que deben cambiarse, priorizarse y centrarse en los principales.

El otro consejo es seguir siendo humilde. Tenga en cuenta que si escribiera algo tan extenso en plazos reales y alguien más viera su código, probablemente también tendrían problemas para entenderlo. Existe una habilidad para escribir código limpio, y hay una habilidad más importante para tratar con el código de otras personas.

El último consejo es tratar de aprovechar el resto de su equipo. Los miembros anteriores pueden conocer información sobre el sistema que usted puede aprender. Además, pueden ser capaces de ayudar a probar los comportamientos. Sé que lo ideal es tener pruebas automatizadas, pero si alguien puede ayudar verificando las cosas por usted, considere la posibilidad de obtenerlas.


Estoy trabajando en una situación similar.

Si no es una pequeña utilidad sino un gran proyecto empresarial, entonces es:

a) demasiado tarde para arreglarlo
b) más allá de las capacidades de una sola persona para intentar a)
c) solo puede solucionarse mediante una reescritura completa de lo que está fuera de cuestión

La refactorización en muchos casos solo puede intentarse en su tiempo privado bajo su propio riesgo. Si no obtiene un mandato explícito para realizarlo como parte de su trabajo diario, es probable que ni siquiera obtenga ningún crédito por ello. Incluso puede ser criticado por "perder tiempo inútilmente en algo que ya ha funcionado perfectamente durante mucho tiempo".

Simplemente continúe pirateando la forma en que lo han hecho antes, reciba su cheque de pago y así sucesivamente. Cuando te sientas completamente frustrado o el sistema llegue al punto de no poder hackear más, encuentra otro trabajo.

EDITAR: Cada vez que trato de abordar la cuestión de la verdadera arquitectura y hacer las cosas de la manera correcta, normalmente entiendo a LOL directamente de los gerentes responsables que dicen algo así como "No me importa la buena arquitectura" (intento traducción del alemán). Personalmente, he llevado un componente muy malo al punto de no ser hackeable y, por supuesto, he dado advertencias avanzadas con meses de antelación. Luego tuvieron que cancelar algunas características prometidas a los clientes porque ya no era factible. Ya nadie lo toca ...


He trabajado este trabajo antes. Pasé poco más de dos años en una bestia legada que es muy similar. Nos tomó dos de nosotros más de un año para estabilizar todo (todavía está roto, pero es mejor).

Lo primero: obtener el inicio de sesión de excepción en la aplicación si aún no existe. Utilizamos FogBugz y tardamos aproximadamente un mes en integrar los informes en nuestra aplicación; no fue perfecto de inmediato, pero estaba reportando errores automáticamente. Por lo general, es bastante seguro implementar bloques try-catch en todos tus eventos, y eso cubrirá la mayoría de tus errores.

A partir de ahí arreglamos los errores que vienen primero. Luego pelea las pequeñas batallas, especialmente aquellas basadas en los insectos. Si soluciona un error que afecta de manera inesperada a otra cosa, refactorice ese bloque para que se desacople del resto del código.

Tomará algunas medidas extremas para reescribir una aplicación grande, crítica para el éxito de la empresa, sin importar lo mala que sea. Incluso si obtiene permiso para hacerlo, pasará demasiado tiempo apoyando la aplicación heredada para hacer cualquier progreso en la reescritura de todos modos. Si haces muchas refactorizaciones pequeñas, eventualmente las grandes no serán tan grandes o tendrás muy buenas clases de base para tu reescritura.

Una cosa que hay que quitar de esto es que es una gran experiencia. Será frustrante, pero aprenderás mucho.


Me encontré (una vez) con un código tan increíblemente enredado que no pude arreglarlo con un duplicado funcional en un tiempo razonable. Sin embargo, ese fue un caso especial, ya que era un analizador y no tenía idea de cuántos clientes podrían estar "utilizando" algunos de los errores que tenía. Renderizar cientos de archivos fuente "de trabajo" erróneos no era una buena opción.

La mayoría de las veces es inminentemente factible, simplemente desalentador. Lea a través de ese libro de refactorización.

Por lo general, comienzo a corregir el código incorrecto moviendo las cosas un poco (sin cambiar realmente el código de implementación más de lo requerido) para que los módulos y las clases sean al menos algo coherentes.

Cuando haya terminado, puede tomar su clase más coherente y reescribir sus agallas para realizar exactamente el mismo modo, pero esta vez con código sensible. Esta es la parte difícil con la administración, ya que generalmente no les gusta saber que tomará semanas codificar y depurar algo que se comportará exactamente igual (si todo va bien).

Durante este proceso, te garantizo que descubrirás toneladas de errores y estupideces de diseño. Está bien corregir errores triviales mientras se está grabando, pero de lo contrario, dejaremos esas cosas para más adelante.

Una vez hecho esto con un par de clases, comenzará a ver cómo se pueden modularizar mejor las cosas, diseñarlas mejor, etc. Además, será más fácil realizar dichos cambios sin afectar las cosas no relacionadas porque el código ahora es más modular, y Probablemente lo sepan a fondo.


Ningún libro podrá cubrir todos los escenarios posibles. También depende de lo que se espera que haga con el proyecto y de si hay algún tipo de especificación externa.

  • Si solo tendrá que hacer pequeños cambios ocasionales, solo haga esos y no se moleste en comenzar a refactorizar.
  • Si hay una especificación (o puede hacer que alguien la escriba), considere una reescritura completa si puede justificarse por la cantidad previsible de cambios en el proyecto
  • Si "la implementación es la especificación" y hay muchos cambios planificados, entonces es bastante difícil. Escriba MUCHAS pruebas unitarias y comience a refactorizar en pequeños pasos.

En realidad, las pruebas unitarias serán invaluables sin importar lo que hagas (si puedes escribirlas en una interfaz que no va a cambiar mucho con refactorizaciones o reescritas, eso es).


Parece que lo estás abordando de la manera correcta.

  • Prueba
  • Refactor
  • Prueba de nuevo

Desafortunadamente, esto puede ser un proceso lento y tedioso. Realmente no hay sustituto para profundizar y entender lo que el código intenta lograr.

Un libro que puedo recomendar (si aún no lo tiene archivado en "etc.") es Refactoring to Patterns . Está dirigido a personas que están en su situación exacta.


Particularmente me gusta el diagrama en Código Completo , en el que comienzas con solo un código heredado, un rectángulo de textura gris difusa. Luego, cuando reemplaza algo de él, tiene un color gris difuso en la parte inferior, blanco sólido en la parte superior y una línea irregular que representa la interfaz entre las dos.

Es decir, todo es ''cosas viejas y desagradables'' o ''cosas nuevas y bonitas''. Un lado de la línea o el otro.

La línea es irregular, porque está migrando diferentes partes del sistema a diferentes velocidades.

A medida que trabaja, la línea irregular desciende gradualmente, hasta que tenga más blanco que gris, y eventualmente solo gris.

Por supuesto, eso no hace que los detalles sean más fáciles para usted. Pero te da un modelo que puedes usar para monitorear tu progreso. En cualquier momento debe tener una comprensión clara de dónde está la línea: qué bits son nuevos, cuáles son antiguos y cómo se comunican los dos lados.


Podría extraer y luego refactorizar una parte de ella, para romper las dependencias y aislar capas en diferentes módulos, bibliotecas, ensamblajes, directorios. Luego, vuelve a inyectar las partes limpias en la aplicación con una estrategia de aplicación de estrangulador . Hacer espuma, enjuagar, repetir.


Puede encontrar útil la siguiente publicación: http://refactoringin.net/?p=36

Como se dice en la publicación, no deseche una sobrescritura completa tan fácilmente. Además, si es posible, intente reemplazar capas enteras o niveles con soluciones de terceros como, por ejemplo, ORM para la persistencia o con código nuevo. Pero lo más importante de todo es tratar de entender la lógica (dominio del problema) detrás del código.


Si sus refactorizaciones están rompiendo cosas, entonces significa que no tiene una cobertura de prueba de unidad adecuada, ya que las pruebas de unidad deberían haberse interrumpido primero. Le recomiendo que obtenga una mejor cobertura de prueba unitaria en segundo lugar, después de colocar el registro de excepciones en su lugar.

Luego, le recomiendo que haga primero las refactorizaciones pequeñas: Método de extracción para dividir los métodos grandes en piezas comprensibles; Introduzca Variable para eliminar alguna duplicación dentro de un método; tal vez introduzca Parámetro si encuentra duplicación entre las variables utilizadas por sus llamantes y la persona que llama.

Y ejecute el conjunto de pruebas de la unidad después de cada refactorización o conjunto de refactorizaciones. Diría que los ejecute todos hasta que adquiera confianza sobre las pruebas que tendrá que repetir cada vez.


Si sus refactorizaciones están rompiendo el código, particularmente el código que parece no estar relacionado, entonces está intentando hacer demasiado a la vez.

Recomiendo una refactorización de primer paso donde todo lo que hace es ExtractMethod: el objetivo es simplemente nombrar cada paso en el código, sin ningún tipo de intento de consolidación.

Después de eso, piense en romper dependencias, reemplazar singletons, consolidación.


Sobre todo, eso suena bastante mal. Pero no entiendo esta parte:

Mis predecesores incluso pensaron que sería inteligente usar todos los conjuntos de datos al revés. Cada actualización de la base de datos se envía directamente al servidor db como parámetros en un procedimiento almacenado, luego los conjuntos de datos se actualizan manualmente para que la interfaz de usuario muestre los cambios más recientes.

Eso suena bastante cerca de la forma en que frecuentemente escribo cosas. ¿Qué pasa con esto? ¿Cuál es la forma correcta?