2009-03-17 16 views
24

He heredado un proyecto en el que los diagramas de clase se parecen mucho a una tela de araña en un plato de espagueti. He escrito alrededor de 300 pruebas unitarias en los últimos dos meses para proporcionarme una red de seguridad que cubra el ejecutable principal.Legacy Code Nightmare

tengo a mi biblioteca de libros de desarrollo ágil a su alcance en cualquier momento dado:

  • trabajo efectivo con el código heredado
  • Refactoring
  • código completo
  • ágil Patrones y prácticas de principios en C#
  • etc.

El problema es todo lo que toco parece romper algo más. Las clases de UI tienen lógica de negocios y código de base de datos mezclados. Existen dependencias mutuas entre varias clases. Hay un par de clases de dioses que se rompen cada vez que cambio alguna de las otras clases. También hay una clase de singleton/utilidad mutante con métodos de media instancia y medios 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 de base de datos 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 tienta pensar que usaron alguna forma de ofuscación débil para la seguridad laboral o como último adiós antes de entregar el código.

¿Hay algún buen recurso para desenmarañar este lío? Los libros que tengo son útiles, pero solo parecen cubrir la mitad de los escenarios con los que me estoy cruzando.

+10

¿Estás trabajando en mi antiguo lugar? ;) – geocoin

Respuesta

23

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 comprender lo que el código está tratando de lograr.

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

+0

Refactoring to Patterns link ahora está roto - Supongo que es por Joshua Kerievsky publicado por Addison Wesley en agosto de 2004. – VictorySaber

+1

@VictorySaber Ese es el libro correcto, pero el enlace parece estar funcionando para mí. –

17

Estoy trabajando en una situación similar.

Si no es una pequeña utilidad, pero un gran proyecto de la empresa, entonces es:

a) demasiado tarde para solucionarlo
b) más allá de las capacidades de una sola persona para intentar una)
c) solo se puede arreglar con una reescritura completa de las cosas que está fuera de la cuestión

La refabricación puede en muchos casos solo intentarse en su tiempo privado bajo su propia responsabilidad. Si no obtiene un mandato explícito para hacerlo como parte de su trabajo diario, entonces 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 funcionó perfectamente durante mucho tiempo".

Simplemente siga pirateando la forma en que ha sido pirateada antes, reciba su cheque de pago, etc. Cuando te sientes completamente frustrado o el sistema alcanza el punto de ser no pirateable, busca otro trabajo.

EDIT: Cuando intento abordar la cuestión de la verdadera arquitectura y hacer las cosas de la manera correcta, generalmente me hago daño en la cara directamente de gerentes responsables que dicen algo así como: "Me importa un comino el bien" arquitectura "(intento de traducción del alemán). Personalmente, he traído un componente muy malo hasta el punto de no piratear, mientras que, por supuesto, he dado avisos avanzados con meses de anticipación. Luego tuvieron que cancelar algunas características prometidas a los clientes porque ya no se podía hacer. Nadie lo toca más ...

+1

¡Mira, mi biografía del 2005 al 2008! –

+1

Así es como el sistema consiguió este camino en primer lugar. –

+0

Bueno, eso es literalmente su asunto. Si saben que ya no pueden modificarse de manera segura sin un esfuerzo de refactorización, y no quieren hacerlo, esa es su decisión. –

1

En su mayoría, eso suena bastante mal. Pero no entiendo esta parte:

Mis predecesores, incluso pensaron que sería ser listo como para usar todos los conjuntos de datos hacia atrás. Cada actualización de base de datos es enviada directamente al servidor de base de datos 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 muy parecido a una forma en que suelo escribir cosas con frecuencia. ¿Qué pasa con esto? ¿Cuál es la forma correcta?

+1

Creo que quiere decir que no conectaron los conjuntos de datos para hacer las actualizaciones de la base de datos y en su lugar escribió SQL ad hoc en la capa de presentación para manejarlos. Usted termina con el código duplicado en todas partes o una clase de "actualizaciones de base de datos" que hace lo que el conjunto de datos podría ser conectado para hacer. –

+0

No he podido lograr el paraíso de la encuadernación de datos sin código en aplicaciones CRUD no triviales. Siempre hay algunas reglas comerciales que no he podido expresar adecuadamente sin romper y escribir algunos códigos de la manera tradicional. Pero podría estar haciendo mal. – recursive

+0

En realidad, lo que quise decir con esa afirmación es que todos los conjuntos de datos se borraron y se volvieron a llenar después de cada operación CRUD. Esto resultó en una desaceleración exponencial con cada registro agregado a una tabla. Con 100 registros, una operación CRUD tomó unos nanosegundos. Con 10000 registros cada operación, sin importar cuán trivial, tomó más de un segundo en completarse. –

4

tengo (una vez) encontrar código que estaba enredado por lo insano que no podía fijar con un duplicado funcional en una cantidad razonable de tiempo. Sin embargo, era una especie de caso especial, ya que era un analizador sintáctico y no tenía idea de cuántos clientes podrían estar "usando" algunos de los errores que tenía. Presentar cientos de archivos fuente "operativos" erróneos no era una buena opción.

La mayor parte del tiempo es inminentemente factible, simplemente desalentador. Lee el libro de refactorización.

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

Una vez hecho esto, puede tomar su clase más coherente y volver a escribir sus agallas para realizar exactamente la misma manera, pero esta vez con un código razonable. Esta es la parte difícil de la gestión, ya que generalmente no les gusta escuchar que se van a tomar semanas para codificar y depurar algo que se comportará exactamente igual (si todo va bien).

Durante este proceso, le garantizo que descubrirá toneladas de errores y pura estupidez en el diseño. Está bien corregir errores triviales durante la recodificación, pero de lo contrario dejarlos para más adelante.

Una vez hecho esto con un par de clases, comenzará a ver dónde se pueden modular mejor las cosas, diseñar mejor, etc. Además, será más fácil hacer tales cambios sin afectar cosas no relacionadas porque el código ahora es más modular, y probablemente lo sepa a fondo.

1

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

  • Si solo tiene que hacer pequeños cambios ocasionales, solo hágalo y no se moleste en comenzar a refactorizar.
  • Si hay una especificación (o se puede conseguir a alguien que lo escriba), considere una reescritura completa si puede ser justificado por la cantidad previsible de cambios al proyecto
  • Si "la aplicación es la especificación" y hay muchos cambios planeados, entonces eres prácticamente una manguera. Escriba LOTES de unit tests y comience a refactorizar en pequeños pasos.

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

1

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

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

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

8

He trabajado anteriormente en este trabajo. Pasé poco más de dos años en una bestia heredada 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: obtenga el registro de excepción en la aplicación si no existe ya. Usamos FogBugz y tardamos aproximadamente un mes en integrar los informes en nuestra aplicación; no fue perfecto de inmediato, pero estaba informando 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í arregla los errores que vienen primero. Luego pelea las pequeñas batallas, especialmente aquellas basadas en los errores. Si arreglas un error que inesperadamente afecta a otra cosa, refactoriza ese bloque para que se desacople del resto del código.

Tomará algunas medidas extremas para volver a escribir una aplicación grande, crítica para la empresa, sin importar lo malo que sea.Incluso si obtienes permiso para hacerlo, pasarás demasiado tiempo apoyando la aplicación heredada para hacer algún progreso en la reescritura de todos modos. Si realiza muchas refactorizaciones pequeñas, con el tiempo las más grandes no serán tan grandes o tendrá muy buenas clases de base para su reescritura.

Una cosa para alejarse de esto es que es una gran experiencia. Será frustrante, pero aprenderá mucho.

+2

Este es un gran comentario. Sin embargo, no estoy de acuerdo con que aprendas mucho. Probablemente solo aprenderá las formas de hacer las cosas mal. Dependiendo de qué tan resistente sea, aprenderá algo de él o simplemente se degradará y olvidará cómo hacer las cosas bien. – User

+0

Ver una implementación deficiente es una buena experiencia de aprendizaje. Se necesita un individuo de alto calibre para no degradarse. Sin embargo, diría que fue gente que olvidó cómo hacer las cosas bien que te llevan a situaciones como esta para empezar. –

+1

"Una cosa para alejar de esto es que es una gran experiencia. Será frustrante pero aprenderás mucho". - Tengo que recordar esas palabras cada vez que quiero gritar ... gracias. –

1

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

Le recomiendo que primero haga pequeñas refactorizaciones - Extraiga el método para descomponer los métodos grandes en piezas comprensibles; Introduzca Variable para eliminar algunas duplicaciones dentro de un método; tal vez introduzca el parámetro si encuentra duplicación entre las variables utilizadas por las personas que llaman y el destinatario.

Y ejecuta el conjunto de pruebas de unidades después de cada refactorización o conjunto de refactorizaciones. Diría que los ejecute todos hasta que gane confianza sobre qué pruebas deberán volver a realizarse cada vez.

+0

Parece que está detectando las roturas, es difícil hacer algo sin romper otra cosa. –

+0

Si es así, me disculpo. Parecía que el uso y la garantía de calidad encontraban las roturas, cuando las pruebas unitarias debían hacerlo. –

0

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

Creo que su enfoque es bueno, pero debe enfocarse en brindar valor comercial (el número de pruebas unitarias no es una medida del valor comercial, pero puede indicarle si está dentro o fuera de la ruta). Es importante haber identificado los comportamientos que deben modificarse, establecer prioridades y centrarse en los más importantes.

El otro consejo es seguir siendo humilde. Tenga en cuenta que si escribió algo tan grande en fechas límites reales y alguien más vio su código, probablemente también tenga problemas para entenderlo. Hay una habilidad para escribir código limpio, y hay una habilidad más importante para tratar 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 puede aprender. Además, es posible que puedan ayudar a evaluar los comportamientos. Sé que lo ideal es tener pruebas automáticas, pero si alguien puede ayudar verificando las cosas para usted, considere la posibilidad de obtener su ayuda.

0

Me gusta especialmente el diagrama en Code Complete, en el que comienza con el código heredado, un rectángulo de textura gris difusa. Luego, cuando reemplazas un poco, tienes un gris borroso en la parte inferior, un blanco sólido en la parte superior y una línea irregular que representa la interfaz entre los dos.

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

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

Mientras trabaja, la línea dentada desciende gradualmente, hasta que tenga más blanco que gris y, finalmente, solo gris.

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

0

Usted puede encontrar el siguiente post útil: http://refactoringin.net/?p=36

Como se dice en el post, no lo hacen descartar una sobreescritura completa tan fácilmente. Además, si es posible, intente reemplazar capas o niveles enteros con una solución de terceros como, por ejemplo, ORM para 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.

+0

El enlace está (efectivamente) roto (el dominio ya no existe). –

0

Puede extraer y luego refactorizar una parte, romper las dependencias y aislar las capas en diferentes módulos, bibliotecas, ensamblajes y directorios. A continuación, vuelva a inyectar las piezas limpiadas en la aplicación con una estrategia strangler application. Enjabona, enjuaga, repite.