Prefacio
Estoy no en busca de una manera de refactorizar una gran clase código espagueti, ese tema se ha tratado en otras preguntas.
Pregunta
Estoy buscando técnicas para comenzar a comprender un archivo de clase escrito por otro compañero de trabajo que abarca más de 4000 líneas y tiene un único método de actualización enorme que es más de 2000 líneas.
Eventualmente espero construir pruebas unitarias para esta clase y refactorizarlas en muchas clases más pequeñas que sigan DRY y el Principio de Responsabilidad Única.
¿Cómo puedo gestionar y abordar esta tarea? Me gustaría poder dibujar un diagrama de los eventos que ocurren dentro de la clase y luego pasar a la funcionalidad de abstracción, pero estoy luchando por obtener una vista de arriba hacia abajo de cuáles son las responsabilidades y dependencias de la clase.
Editar: en las respuestas que las personas ya han cubierto temas relacionados con los parámetros de entrada, esta información es para los recién llegados:
En este caso, el método principal de la clase no tiene parámetros de entrada y ejecuta un ciclo while hasta que se le indique que se detenga. El constructor tampoco toma parámetros.
Esto se suma a la confusión de la clase, sus requisitos y dependencias. La clase obtiene referencias a otras clases a través de métodos estáticos, singletons y alcanzando clases a las que ya tiene referencia.
Respuestas:
Curiosamente, la refactorización es la forma más eficiente que he encontrado para comprender un código como este. No tienes que hacerlo limpiamente al principio. Puede hacer un análisis de refactorización de análisis rápido y sucio, luego revertirlo y hacerlo con más cuidado con las pruebas unitarias.
La razón por la que esto funciona es porque la refactorización realizada correctamente es una serie de pequeños cambios, casi mecánicos, que puede hacer sin comprender realmente el código. Por ejemplo, puedo ver un blob de código que se repite en todas partes y factorizarlo en un método, sin necesidad de saber cómo funciona. Podría darle un nombre tonto al principio como
step1
o algo así. Entonces me doy cuenta de esostep1
ystep7
con frecuencia aparecen juntos, y lo factorizo. Entonces, de repente, el humo se ha disipado lo suficiente como para que realmente pueda ver el panorama completo y hacer algunos nombres significativos.Para encontrar la estructura general, he encontrado que crear un gráfico de dependencia a menudo ayuda. Hago esto manualmente usando graphviz ya que he encontrado que el trabajo manual me ayuda a aprenderlo, pero probablemente hay herramientas automatizadas disponibles. Aquí hay un ejemplo reciente de una característica en la que estoy trabajando. He descubierto que esta también es una forma efectiva de comunicar esta información a mis colegas.
fuente
Primero, una palabra de precaución (aunque no sea lo que está preguntando) antes de responder a su pregunta: una pregunta tremendamente importante es por qué¿Quieres refactorizar algo? El hecho de que mencione "compañero de trabajo" plantea la pregunta: ¿Hay alguna razón comercial para el cambio? Especialmente en un entorno corporativo, siempre habrá códigos heredados e incluso nuevos códigos con los que no estará satisfecho. El hecho es que no hay dos ingenieros que resuelvan un problema dado con la misma solución. Si funciona y no tiene implicaciones de rendimiento horribles, ¿por qué no dejarlo así? El trabajo de características generalmente es mucho más beneficioso que la refactorización porque no le gusta particularmente una implementación. Dicho esto, hay algo que decir sobre la deuda técnica y la limpieza del código que no se puede mantener. Mi única sugerencia aquí es que ya has hecho la distinción y has comprado para hacer los cambios, de lo contrario es probable que te preguntendespués de haberse hundido en un gran esfuerzo y probablemente obtendrá mucho más (y comprensible) retroceso.
Antes de abordar una refactorización, siempre quiero asegurarme de tener una comprensión firme de lo que estoy cambiando. Generalmente, utilizo dos métodos para abordar este tipo de cosas: registro y depuración. Esto último se ve facilitado por la existencia de pruebas unitarias (y pruebas del sistema si tienen sentido). Como no tiene ninguno, le recomiendo encarecidamente que escriba pruebas con cobertura de sucursal completa para asegurarse de que sus cambios no creen cambios de comportamiento inesperados. Las pruebas unitarias y el paso por el código lo ayudan a obtener una comprensión de bajo nivel del comportamiento en un entorno controlado. El registro le ayuda a comprender mejor el comportamiento en la práctica (esto es especialmente útil en situaciones multiproceso).
Una vez que consiga una mejor comprensión de todo lo que está pasando a través de depuración y registro, a continuación, voy a empezar la lectura de códigos. En este punto, generalmente tengo una mejor comprensión de lo que está sucediendo y sentarme y leer 2k LoC debería tener mucho más sentido en ese punto. Mi intención aquí es más para obtener una vista de extremo a extremo y asegurarme de que no haya ningún tipo de casos extraños que las pruebas unitarias que he escrito no estén cubriendo.
Luego pensaré en el diseño y redactaré uno nuevo. Si la compatibilidad con versiones anteriores es importante, me aseguraré de que la interfaz pública no cambie.
Con una comprensión firme de lo que está sucediendo, las pruebas y un nuevo diseño nuevo en la mano, lo someteré a revisión. Una vez que el diseño haya sido examinado por al menos dos personas (espero que estén familiarizadas con el código), comenzaré a implementar los cambios.
Espero que no sea tan dolorosamente obvio y que ayude.
fuente
No hay una receta general, pero algunas reglas generales ( suponiendo un lenguaje estáticamente tipado pero que realmente no debería importar):
1) Echa un vistazo a la firma del método. Se da a conocer lo que pasa en y es de esperar que viene a cabo . Desde el estado general de esta clase de dios , supongo que este es un primer punto de dolor . Supongo que se usa más de un parámetro.
2) Utilice la función de búsqueda de su editor / EDI para determinar Exit-Points (por lo general un retorno se utiliza statement_)
De lo que sabe, qué necesita la función para las pruebas y qué espera a cambio .
Entonces, una primera prueba simple sería llamar a la función con los parámetros necesarios y esperar que el resultado no sea nulo . Eso no es mucho, pero es un punto de partida.
A partir de eso, podría entrar en un círculo hermenéutico (un término acuñado por HG Gadamer, un filósofo alemán). El punto es: ahora tiene una comprensión rudimentaria de la clase y actualiza esta comprensión con nuevos conocimientos detallados y tiene una nueva comprensión de toda la clase.
Esto combinado con el método científico : hacer suposiciones y ver si se cumplen.
3) Tome un parámetro y mire, en qué parte de la clase se transforma de alguna manera:
Por ejemplo, estás haciendo Java como yo, por lo general hay getter y setter que puedes buscar. SearchPattern
$objectname
. (o$objectname\.(get|set)
si estás haciendo Java)Ahora podría hacer más suposiciones sobre lo que hace el método.
Rastree solo los parámetros de entrada ( primero ) cada uno a través del método. Si es necesario, haga algunos diagramas o tablas , donde escriba cada cambio en cada una de las variables.
A partir de eso, puede escribir más pruebas, describiendo el comportamiento del método. Si tiene una comprensión aproximada de cómo se transforma cada parámetro de entrada a lo largo del método, comience a experimentar : pase nulo para un parámetro o entrada extraña . Haga suposiciones, verifique el resultado y varíe los aportes y las suposiciones.
Si hace esto una vez, tiene una "tonelada" de pruebas que describen el comportamiento de su método.
4) En el siguiente paso, buscaría dependencias : ¿qué necesita el método además de su entrada para funcionar correctamente ? ¿Hay posibilidades de reducir o reestructurar esos? Cuantas menos dependencias tengas, más claro verás los puntos, dónde hacer las primeras divisiones.
5) A partir de ahí, puede recorrer todo el camino de refactorización con patrones de refactorización y refactorización a patrones.
Aquí un buen Vid: GoGaRuCo 2014- El método científico de resolución de problemas Se trata de la resolución de problemas, pero útil para una metodología general de comprensión de cómo funciona algo .
Usted menciona que la función llamada no tiene parámetros de entrada : en este caso especial, primero trataría de identificar las dependencias y refactorizarlas a parámetros, para que pueda intercambiarlas como desee.
fuente
Usted menciona un
update
método de 2000 líneas .Empezaría a leer este método de arriba a abajo. Cuando encuentre un conjunto de declaraciones que se pertenecen entre sí, es decir, comparten una responsabilidad (por ejemplo, crear un usuario), extraiga estas declaraciones en una función.
De esta manera, no cambia la forma en que funciona el código, pero usted
Repita estos pasos para otros métodos.
fuente