java - tecnicas - Refactorización de código en mal diseño de sistema
tecnicas de refactorizacion de codigo (14)
Como otros han notado, no cambies algo que funcione solo para hacerlo más bonito. El riesgo de que introduzca errores es excelente.
Mi filosofía es: como tengo que hacer cambios para satisfacer nuevos requisitos o corregir errores reportados, trato de hacer que la parte del código que tengo que cambiar sea un poco más limpia. Voy a tener que probar el código cambiado de todos modos, ahora es un buen momento para hacer una pequeña limpieza a un pequeño costo adicional.
Los cambios de diseño fundamentales son los más difíciles y deben guardarse para ocasiones en las que tenga que hacer un cambio lo suficientemente grande como para que pruebe todos los códigos modificados de todos modos.
Cambiar el diseño de una base de datos incorrecta es el más difícil de todos porque es probable que muchos programas utilicen las tablas mal diseñadas. Cualquier cambio en la base de datos requiere cambiar cada programa que lo lea o lo escriba. La mejor manera de lograr esto es, por lo general, tratar de reducir el número de lugares que acceden a cualquier parte determinada de la base de datos. Para tomar un ejemplo simple: supongamos que hay 20 lugares que leen los registros del cliente y calculan el saldo de la cuenta del cliente. Reemplace esto con una función que lea la base de datos y devuelva el total y veinte llamadas a esa función. Ahora puede cambiar el esquema para los registros del cliente y solo hay un fragmento de código para cambiar en lugar de 20. El principio es bastante simple, pero en la práctica es poco probable que cada función que accede a un registro determinado haga lo mismo. Incluso si el programador original fue lo suficientemente torpe como para escribir el mismo código 20 veces (no es poco probable, he visto mucho de eso), la situación real probablemente no es que haya escrito 1 función 20 veces, punto, sino que haya escrito la función A 20 veces, la función B 12 veces, la función C 4 veces, etc.
Soy un ingeniero de software junior al que se le ha encomendado la tarea de hacerse cargo de un sistema anterior. Este sistema tiene varios problemas, basados en mi evaluación preliminar.
- código de espagueti
- código repetitivo
- clases con 10k líneas y más
- mal uso y over-logging usando log4j
- diseño de tabla de base de datos malo
- Falta control de fuente -> He configurado Subversion para esto
- Documentos faltantes -> No tengo idea de la regla de negocios, excepto para leer los códigos
¿Cómo debo hacerlo para mejorar la calidad del sistema y resolver esos problemas? Puedo pensar en usar software de análisis de código estático para resolver cualquier mala práctica de codificación.
Sin embargo, no puede detectar problemas o problemas de diseño incorrectos. ¿Cómo debo resolver estos problemas paso a paso?
En primer lugar, asegúrese de tener instalado el sistema de control de fuente y de que todo el código fuente esté versionado y pueda compilarse.
Luego, puede intentar escribir la prueba unitaria para las partes centrales de su sistema. A partir de ahí, cuando tenga un cuerpo más o menos sólido de pruebas de regresión, puede proceder con la refactorización.
Cuando encuentro una base de código desordenada, generalmente empiezo por cambiar el nombre de los tipos y métodos mal nombrados para reflejar mejor su intención inicial. Luego puede intentar dividir métodos enormes en otros más pequeños.
En primer lugar, permítanme decir que Working Effectively with Legacy Code es probablemente un libro realmente bueno para leer, a juzgar por tres respuestas en un minuto el uno del otro.
- diseño de tabla de base de datos malo
Este, probablemente estés atrapado. Si intenta cambiar un diseño de base de datos existente, probablemente se comprometa a rediseñar todo el sistema y a escribir herramientas de migración para los datos existentes. Dejar las cosas como están.
Enfócate primero en la estabilidad. No puede mejorar o refactorizar hasta que tenga algún tipo de entorno estable en el lugar alrededor de la aplicación.
Algunos pensamientos:
- Control de revisión Has comenzado desde la creación de la subversión. Ahora asegúrese de que sus esquemas de base de datos, procedimientos almacenados, scripts, componentes de terceros, etc. también están bajo control de revisión. Tenga un sistema de etiquetado de versiones, asegúrese de etiquetar las versiones y pueda acceder con precisión a las versiones anteriores en el futuro.
- Construir y liberar Cree una forma de crear lanzamientos estables en una máquina que no sea su máquina de desarrollo. Es posible que desee utilizar ant / nant, make, msbuild, o incluso un archivo por lotes o script de shell. Es posible que también necesite scripts / instaladores de implementación si no existen.
- Ponlo bajo prueba . No cambie la aplicación hasta que tenga una forma de saber si su cambio la ha roto. Para esto necesitas pruebas. Es de esperar que pueda escribir pruebas de unidad xunit para algunas de las clases más simples e independientes, pero intente crear algunas pruebas de sistema / integración que ejerzan la aplicación como un todo. Sin una alta cobertura de código (que no tendrá que comenzar), las pruebas de integración son su mejor opción. Adquiera el hábito de realizar las pruebas con la mayor frecuencia posible. Aproveche cada oportunidad para extenderlos.
- Haga cambios pequeños y enfocados . Intente identificar sistemas / subsistemas dentro de la aplicación y mejore los límites entre ellos. Esto reduce los efectos colaterales de los cambios que puede realizar. Tenga cuidado con la tentación de "perfeccionar" el código reformatándolo o imponiendo el último patrón de diseño de moda. Dar la vuelta a un sistema como este lleva tiempo .
- Documentación . Es necesario, pero no te preocupes demasiado por eso. La documentación del sistema rara vez se utiliza en mi experiencia. Las buenas pruebas suelen ser mejores que una buena documentación. Concéntrese en documentar las interfaces entre la aplicación y el contexto del sistema en el que se ejecuta (entradas, salidas, estructuras de archivos, esquemas db, etc.).
- Administrar las expectativas Si está en mal estado, es probable que se resista a sus esfuerzos para realizar cambios y las escalas de tiempo pueden ser más difíciles de lo normal para estimar. Asegúrese de que la administración y las partes interesadas entiendan eso.
A toda costa, ten cuidado con la tentación de simplemente reescribir todo. Casi nunca es lo correcto en esta situación. Si funciona, concéntrate en mantenerlo funcionando.
Como desarrollador junior, no tengas miedo de pedir ayuda. Como otros han dicho, Working Effectively With Legacy Code es un buen libro para leer, al igual que Martin Fowler''s Refactoring .
¡Buena suerte!
Intenta crear algunas pruebas unitarias primero que puedan desencadenar algunas acciones en tu código.
Cometa todo en SVN y marque TAG (en caso de que algo vaya mal, tendrá una cápsula de escape).
Utilice el plugin de Eclipse de código en http://www.intooitus.com/inCode.html y busque qué refactorizaciones propone. Compruebe si las refactorizaciones propuestas parecen estar bien para su problema. Intenta entenderlos.
Vuelve a probar con las unidades creadas antes.
Ahora puede usar FindBugs y / o PMD para verificar otros problemas sutiles.
Si todo está en orden, es posible que desee volver a registrarse.
También trataría de leer la fuente para detectar algunos casos en los que se pueden aplicar patrones.
Los problemas de diseño son muy difíciles de atrapar. El primer lugar para comenzar es comprender el diseño de la aplicación. Me parece útil diagramar usando UML o un diagrama de flujo del proceso, cualquier cosa que comunique el diseño y el trabajo para la aplicación.
A partir de ahí, entraré en más detalles, y me haré las preguntas "¿Lo habría hecho de esta manera?", ¿Qué otras opciones hay? Es fácil ver la deuda del código, es decir, la deuda que obtenemos al tomar malas decisiones, como siempre es malo, pero a veces hay otros factores como el presupuesto, el tiempo, la disponibilidad de recursos, etc. Hay que hacer la pregunta si Vale la pena refactorizar una aplicación que funciona pero está mal diseñada.
Si hay muchas nuevas funciones, cambios, correcciones de errores, etc., diría que es bueno refactorizar, pero si la aplicación rara vez cambia y es estable, entonces dejarlo como está es un mejor enfoque.
Otro punto a tener en cuenta es que si el código es utilizado por otra aplicación como un servicio o módulo, la refactorización podría significar primero crear un apéndice alrededor del código que los servidores como interfaces, una vez que se defina claramente y tenga una unidad de prueba para probarlo trabajo. Puede elegir cualquier tecnología para completar los detalles.
Mi respuesta estándar a esta pregunta es: Refactorizar la Fruta Baja . En este caso, me inclinaría a tomar una de las clases de 10K-line y buscar oportunidades para Sprout Class , pero esa es solo mi propia propensión; es posible que se sienta más cómodo cambiando otras cosas primero (configurar el control de fuente fue un excelente primer paso). Pruebe lo que pueda; refactorizar lo que no se puede probar, dar un paso a la vez y hacerlo mejor.
Tenga en cuenta a medida que progresa cuánto mejor está haciendo las cosas; si te concentras solo en cuán malas son las cosas, es probable que te desanimes.
Obtenga y lea Trabajando eficazmente con código heredado . Se trata exactamente de esta situación.
Como otros también han aconsejado, para la refactorización necesita un conjunto sólido de pruebas unitarias. Sin embargo, el código heredado es típicamente muy difícil de probar en una unidad como es, ya que no se ha escrito para que pueda ser comprobado por la unidad. Entonces, primero tiene que refactorizar para permitir las pruebas unitarias, lo que le permitiría comenzar la refactorización ... una mala captura.
Aquí es donde el libro te ayudará. Proporciona muchos consejos prácticos sobre cómo hacer que la unidad de código mal diseñada sea comprobable con los cambios de código mínimos y más seguros posibles. Las refactorizaciones automáticas también pueden ayudarlo aquí, pero hay trucos descritos en el libro que solo se pueden hacer a mano. Luego, una vez que el primer conjunto de pruebas unitarias esté en su lugar, puede comenzar a refactorizar gradualmente hacia un código mejor y más fácil de mantener.
Actualización: para obtener sugerencias sobre cómo tomar el control del código heredado, es posible que esta respuesta mía anterior sea útil.
Como señaló @Alex, las pruebas unitarias también son muy útiles para comprender y documentar el comportamiento real del código. Esto es especialmente útil cuando la documentación sobre el sistema es inexistente o desactualizada.
Primero escriba algunas pruebas unitarias y asegúrese de que pasen. Luego, con cada cambio de refactorización que realice, solo asegúrese de que las pruebas sigan pasando. Entonces puede estar seguro de que su comportamiento de aplicación al mundo exterior no ha cambiado.
Esto también tiene el beneficio adicional de que las pruebas siempre estarán allí, por lo que para cualquier cambio futuro las pruebas deben pasar, evitando cualquier regresión en los nuevos cambios.
Primero, no arregles lo que no está roto. Siempre que el sistema que vaya a asumir el control funcione, deje la funcionalidad solo.
Sin embargo, el sistema está obviamente roto en lo que respecta a la capacidad de mantenimiento, así que eso es lo que aborda. Como se mencionó anteriormente, primero escriba algunas pruebas, haga una copia de seguridad de la fuente en un cvs, y LUEGO comience limpiando primero las piezas pequeñas, luego las más grandes, y así sucesivamente. NO ataque los problemas arquitectónicos más grandes hasta que haya adquirido una buena comprensión de cómo funciona el sistema. Las herramientas no te ayudarán si no te sumerges en el código, pero cuando lo haces, ayudan mucho.
Recuerde, nada es "perfecto". No sobreinnovar. Obedece los principios de KISS y YAGNI .
EDIT:
agregó un enlace directo al artículo de YAGNI
Su problema n. ° 7 es de lejos el más importante. Mientras no tenga idea de cómo se supone que se comporta el sistema , todas las consideraciones técnicas son secundarias. Todo el mundo sugiere pruebas unitarias, pero ¿cómo se puede escribir una prueba útil si no se puede distinguir entre el comportamiento deseado y el no deseado?
Por lo tanto, antes de comenzar a tocar el código, debe comprender el sistema desde el punto de vista del usuario: hable con los usuarios, obsérvelos utilizando el sistema y escriba la documentación en el nivel de caso de uso.
Sí, estoy sugiriendo seriamente que pase días, más probablemente semanas, sin cambiar una sola línea de código. Porque en este momento, cualquier cambio que hagas probablemente rompa las cosas sin que te des cuenta.
Una vez que comprenda la aplicación, al menos sabrá qué funcionalidad es importante probar (de forma manual o automática).
Tenga en cuenta que este sistema heredado, con todo su código de espagueti, funciona actualmente. No cambies las cosas solo porque no se ven tan bonitas como deberían. Concéntrese en la estabilidad, las nuevas funciones y la familiaridad antes de extraer el antiguo código hacia la izquierda y hacia la derecha.
Un buen libro sobre este tema es Trabajar eficazmente con Legacy Code Por Michael Feathers (2004). Pasa por el proceso de hacer pequeños cambios, mientras trabaja para una mayor limpieza.
- Escriba la prueba de la unidad y encuentre y elimine el código duplicado.
- Escriba la prueba de la unidad y rompa los métodos largos en una serie de métodos cortos.
- Escriba la prueba de unidad y encuentre y elimine el método duplicado.
- Escriba las pruebas de unidad y divida las clases para que el siguiente siga el principio de responsabilidad individual .
Trabajar eficazmente con Legacy Code podría ser útil.