ventajas sirve que para librerias funciones ejemplos dev desventajas codigos caracteristicas c++ version-control maintenance anti-patterns

sirve - ¿Qué hacer con un archivo fuente de C++ de 11000 líneas?



librerias de c++ (30)

  1. No vuelvas a tocar este archivo y el código de nuevo!
  2. Tratar es como algo con lo que estás atrapado. Comience a escribir adaptadores para la funcionalidad codificada allí.
  3. Escriba el nuevo código en diferentes unidades y hable solo con los adaptadores que encapsulan la funcionalidad del monstruo.
  4. ... si solo uno de los anteriores no es posible, renuncie al trabajo y obtenga uno nuevo.

Así que tenemos este enorme archivo fuente (¿tienen 11000 líneas?) Mainmodule.cpp en nuestro proyecto y cada vez que tengo que tocarlo me estremezco.

Como este archivo es tan central y grande, sigue acumulando cada vez más código y no puedo pensar en una buena manera de hacer que empiece a reducirse.

El archivo se usa y se cambia activamente en varias (> 10) versiones de mantenimiento de nuestro producto, por lo que es realmente difícil refactorizarlo. Si tuviera que "simplemente" dividirlo, digamos, para empezar, en 3 archivos, luego fusionar los cambios de las versiones de mantenimiento se convertirá en una pesadilla. Y también si divide un archivo con un historial tan extenso y rico, el seguimiento y la comprobación de los cambios antiguos en el historial de SCC repente se vuelve mucho más difícil.

Básicamente, el archivo contiene la "clase principal" (despacho y coordinación del trabajo interno principal) de nuestro programa, por lo que cada vez que se agrega una característica, también afecta a este archivo y cada vez que crece. :-(

¿Qué haría usted en esta situación? ¿Alguna idea sobre cómo mover nuevas funciones a un archivo fuente separado sin desordenar el flujo de trabajo de SCC ?

(Nota sobre las herramientas: usamos C ++ con Visual Studio ; usamos AccuRev como SCC pero creo que el tipo de SCC realmente no importa aquí; usamos Araxis Merge para hacer una comparación real y la fusión de archivos)


  1. Encuentre algún código en el archivo que sea relativamente estable (que no cambie rápidamente, y que no varíe mucho entre sucursales) y que pueda ser una unidad independiente. Mueva esto a su propio archivo y, para el caso, a su propia clase, en todas las ramas. Debido a que es estable, esto no causará (muchas) combinaciones "incómodas" que deben aplicarse a un archivo diferente del que se crearon originalmente, cuando combina el cambio de una rama a otra. Repetir.

  2. Encuentre algún código en el archivo que, básicamente, solo se aplique a un pequeño número de sucursales y podría ser independiente. No importa si está cambiando rápido o no, debido a la pequeña cantidad de sucursales. Mueve esto a sus propias clases y archivos. Repetir.

Por lo tanto, hemos eliminado el código que es el mismo en todas partes y el código que es específico para ciertas sucursales.

Esto te deja con un núcleo de código mal administrado: se necesita en todas partes, pero es diferente en cada rama (y / o cambia constantemente para que algunas se ejecuten detrás de otras) y, sin embargo, está en un solo archivo. Sin éxito tratando de fusionarse entre las ramas. Para de hacer eso. Ramifique el archivo permanentemente , tal vez cambiándolo de nombre en cada rama. Ya no es "principal", es "principal para la configuración X". De acuerdo, perderá la capacidad de aplicar el mismo cambio a varias sucursales mediante la fusión, pero esto es en todo caso el núcleo del código donde la fusión no funciona muy bien. Si tiene que administrar manualmente las fusiones de todos modos para hacer frente a los conflictos, no es una pérdida aplicarlas manualmente de forma independiente en cada rama.

Creo que te equivocas al decir que el tipo de SCC no importa, porque, por ejemplo, las habilidades de fusión de git son probablemente mejores que la herramienta de fusión que estás usando. Entonces, el problema central, "fusionar es difícil" ocurre en diferentes momentos para diferentes SCC. Sin embargo, es poco probable que pueda cambiar los SCC, por lo que el problema es probablemente irrelevante.


Bueno, entiendo tu dolor :) También he estado en algunos de estos proyectos y no es bonito. No hay una respuesta fácil para esto.

Un enfoque que puede funcionar para usted es comenzar a agregar protecciones seguras en todas las funciones, es decir, verificar los argumentos, las condiciones previas y posteriores de los métodos y luego agregar las pruebas unitarias para capturar la funcionalidad actual de las fuentes. Una vez que tenga esto, estará mejor equipado para modificar el código porque tendrá avisos y errores que le alertarán si ha olvidado algo.

A veces, aunque hay ocasiones en que la refactorización puede traer más dolor que beneficio. Entonces puede ser mejor dejar el proyecto original y en un estado de pseudo mantenimiento y comenzar desde cero y luego agregar gradualmente la funcionalidad de la bestia.


Confucio dice: "el primer paso para salir del agujero es dejar de cavar el agujero".


Creo que sería mejor crear un conjunto de clases de comando que se asignen a los puntos API de mainmodule.cpp.

Una vez que estén en su lugar, deberá refactorizar la base de código existente para acceder a estos puntos API a través de las clases de comando; una vez que haya terminado, puede refactorizar la implementación de cada comando en una nueva estructura de clase.

Por supuesto, con una única clase de 11 KLOC, el código allí probablemente sea altamente acoplado y quebradizo, pero la creación de clases de comando individuales ayudará mucho más que cualquier otra estrategia de proxy / fachada.

No envidio la tarea, pero a medida que pasa el tiempo este problema solo empeorará si no se aborda.

Actualizar

Sugeriría que el patrón de Comando es preferible a una Fachada.

Es preferible mantener / organizar muchas clases de comando diferentes en una fachada (relativamente) monolítica. El mapeo de una sola Fachada en un archivo KLOC 11 probablemente tendrá que dividirse en algunos grupos diferentes.

¿Por qué molestarse en tratar de averiguar estos grupos de fachada? Con el patrón de Comando podrá agrupar y organizar estas pequeñas clases orgánicamente, para que tenga mucha más flexibilidad.

Por supuesto, ambas opciones son mejores que el archivo único 11 KLOC y en crecimiento.


Déjeme adivinar: ¿Diez clientes con conjuntos de características divergentes y un gerente de ventas que promueve la "personalización"? He trabajado en productos como ese antes. Teníamos esencialmente el mismo problema.

Reconoce que tener un archivo enorme es un problema, pero aún más problemas son las diez versiones que debe mantener "actual". Eso es mantenimiento múltiple. SCC puede hacerlo más fácil, pero no puede hacerlo bien.

Antes de intentar dividir el archivo en partes, debe volver a sincronizar las diez ramas para que pueda ver y configurar todo el código a la vez. Puede hacer esto una rama a la vez, probando ambas ramas contra el mismo archivo de código principal. Para imponer el comportamiento personalizado, puede usar #ifdef y amigos, pero es mejor usar lo más común posible si / else contra constantes definidas. De esta manera, su compilador verificará todos los tipos y probablemente eliminará el código objeto "muerto" de todos modos. (Sin embargo, es posible que desee desactivar la advertencia sobre el código muerto).

Una vez que solo hay una versión de ese archivo compartida implícitamente por todas las ramas, entonces es bastante más fácil comenzar con los métodos tradicionales de refactorización.

Los #ifdefs son principalmente mejores para las secciones donde el código afectado solo tiene sentido en el contexto de otras personalizaciones por rama. Se puede argumentar que estos también presentan una oportunidad para el mismo esquema de fusión de ramas, pero no se vuelven locos. Un proyecto colosal a la vez, por favor.

A corto plazo, el archivo parecerá crecer. Esto esta bien. Lo que estás haciendo es reunir cosas que necesitan estar juntas. Luego, comenzarás a ver áreas que son claramente iguales independientemente de la versión; Estos pueden ser dejados solos o refaccionados a voluntad. Otras áreas diferirán claramente dependiendo de la versión. Tienes una serie de opciones en este caso. Un método es delegar las diferencias a los objetos de estrategia por versión. Otra es derivar versiones de cliente de una clase abstracta común. Pero ninguna de estas transformaciones es posible siempre y cuando tengas diez "consejos" de desarrollo en diferentes ramas.



Esto no es una respuesta al gran problema, sino una solución teórica a una parte específica del mismo:

  • Averigua dónde quieres dividir el archivo grande en subarchivos. Ponga los comentarios en algún formato especial en cada uno de esos puntos.

  • Escriba un script bastante trivial que dividirá el archivo en subarchivos en esos puntos. (Tal vez los comentarios especiales tienen nombres de archivo incrustados que el script puede usar como instrucciones para dividirlo). Debería preservar los comentarios como parte de la división.

  • Ejecutar el script. Eliminar el archivo original.

  • Cuando necesite fusionar desde una rama, primero vuelva a crear el archivo grande concatenando de nuevo las piezas, haga la fusión y luego divídalo nuevamente.

Además, si desea conservar el historial de archivos SCC, espero que la mejor manera de hacerlo sea indicando a su sistema de control de origen que los archivos de piezas individuales son copias del original. Luego conservará el historial de las secciones que se guardaron en ese archivo, aunque, por supuesto, también registrará que las partes grandes se "eliminaron".


La única solución que he imaginado a tales problemas sigue. La ganancia real por el método descrito es la progresividad de las evoluciones. No hay revoluciones aquí, de lo contrario estarás en problemas muy rápido.

Inserte una nueva clase de cpp sobre la clase principal original. Por ahora, básicamente redirigirá todas las llamadas a la clase principal actual, pero apuntará a hacer que la API de esta nueva clase sea lo más clara y concisa posible.

Una vez hecho esto, tienes la posibilidad de agregar nuevas funcionalidades en nuevas clases.

En cuanto a las funcionalidades existentes, debe moverlas progresivamente a nuevas clases a medida que se vuelvan lo suficientemente estables. Perderá la ayuda de SCC para este fragmento de código, pero no hay mucho que se pueda hacer al respecto. Sólo tienes que elegir el momento adecuado.

Sé que esto no es perfecto, aunque espero que pueda ayudar, ¡y el proceso debe adaptarse a sus necesidades!

Información Adicional

Tenga en cuenta que Git es un SCC que puede seguir fragmentos de código de un archivo a otro. He escuchado cosas buenas al respecto, por lo que podría ayudar mientras avanza progresivamente su trabajo.

Git se construye alrededor de la noción de manchas que, si entiendo correctamente, representan partes de archivos de código. Mueve estas piezas en diferentes archivos y Git las encontrará, incluso si las modificas. Aparte del video de Linus Torvalds mencionado en los comentarios a continuación, no he podido encontrar algo claro sobre esto.


La fusión no será una pesadilla tan grande como lo será cuando obtenga 30000 archivos LOC en el futuro. Asi que:

  1. Deja de agregar más código a ese archivo.
  2. Dividirlo.

Si no puede detener la codificación durante el proceso de refactorización, puede dejar este archivo grande como está por un tiempo, al menos sin agregarle más código: ya que contiene una "clase principal" que podría heredar de ella y mantener la clase heredada ( es) con funciones sobrecargadas en varios archivos nuevos, pequeños y bien diseñados.


Lo que tienes es un ejemplo clásico de un diseño conocido antipattern llamado blob . Tómese un tiempo para leer el artículo que señalo aquí, y quizás pueda encontrar algo útil. Además, si este proyecto es tan grande como parece, debe considerar algún diseño para evitar que se convierta en un código que no pueda controlar.


Me parece que estás enfrentando una serie de olores de código aquí. En primer lugar, la clase principal parece violar el principio abierto / cerrado . También parece que está manejando demasiadas responsabilidades . Debido a esto, asumiría que el código es más frágil de lo que debe ser.

Si bien puedo entender sus inquietudes con respecto a la trazabilidad después de una refactorización, espero que esta clase sea bastante difícil de mantener y mejorar y que cualquier cambio que realice probablemente cause efectos secundarios. Asumiría que el costo de estos es mayor que el costo de refactorizar la clase.

En cualquier caso, dado que el código de los olores empeorará con el tiempo, al menos en algún momento el costo de estos será mayor que el costo de la refactorización. A partir de su descripción, asumo que ha pasado el punto de inflexión.

Refactorizando esto se debe hacer en pequeños pasos. Si es posible, agregue pruebas automatizadas para verificar el comportamiento actual antes de refactorizar algo. Luego seleccione áreas pequeñas de funcionalidad aislada y extraiga estos como tipos para delegar la responsabilidad.

En cualquier caso, suena como un gran proyecto, así que buena suerte :)


No debe preocuparse por reducir el tamaño del archivo, sino por reducir el tamaño de la clase. Todo se reduce a lo mismo, pero te hace ver el problema desde un ángulo diferente (como suggests @Brian Rasmussen, tu clase parece tener muchas responsabilidades).


No sé si esto resuelve su problema, pero supongo que lo que quiere hacer es migrar el contenido del archivo a archivos más pequeños e independientes entre sí (resumido). Lo que también entiendo es que tienes alrededor de 10 versiones diferentes del software flotando y necesitas apoyarlas todas sin desordenar las cosas.

En primer lugar, no hay forma de que esto sea fácil y se resolverá solo en unos minutos de lluvia de ideas. Las funciones vinculadas en su archivo son todas vitales para su aplicación, y simplemente cortarlas y migrarlas a otros archivos no salvará su problema.

Creo que solo tienes estas opciones:

  1. No emigres y quédate con lo que tienes. Posiblemente abandone su trabajo y comience a trabajar en un software serio con un buen diseño además. La programación extrema no siempre es la mejor solución si está trabajando en un proyecto a largo plazo con fondos suficientes para sobrevivir a una falla o dos.

  2. Calcule un diseño de cómo le gustaría que se vea su archivo una vez que se haya dividido. Crea los archivos necesarios e integralos en tu aplicación. Cambie el nombre de las funciones o sobrecargue para tomar un parámetro adicional (¿quizás solo un simple booleano?). Una vez que tenga que trabajar en su código, migre las funciones que necesita para trabajar en el nuevo archivo y asigne las llamadas de función de las funciones antiguas a las funciones nuevas. Aún debe tener su archivo principal de esta manera, y aún ser capaz de ver los cambios que se le hicieron, una vez que se trata de una función específica que sabe exactamente cuándo fue subcontratada y así sucesivamente.

  3. Intente convencer a sus compañeros de trabajo con un buen pastel de que el flujo de trabajo está sobrevalorado y que necesita volver a escribir algunas partes de la aplicación para hacer negocios serios.


OK, en su mayor parte, la reescritura de la API del código de producción es una mala idea como comienzo. Dos cosas deben suceder.

Primero, debe hacer que su equipo decida congelar el código en la versión de producción actual de este archivo.

Dos, debe tomar esta versión de producción y crear una rama que administre las compilaciones utilizando directivas de preproceso para dividir el archivo grande. Dividir la compilación utilizando directivas de preprocesador JUST (#ifdefs, #includes, #endifs) es más fácil que recodificar la API. Definitivamente es más fácil para sus SLA y soporte continuo.

Aquí, simplemente puede cortar las funciones que se relacionan con un subsistema particular dentro de la clase y ponerlas en un archivo, digamos mainloop_foostuff.cpp, e incluirlo en mainloop.cpp en la ubicación correcta.

O

Una forma más robusta y más lenta de tiempo sería diseñar una estructura de dependencias internas con doble dirección en la forma en que se incluyen las cosas. Esto le permitirá dividir las cosas y seguir cuidando las codependencias. Tenga en cuenta que este enfoque requiere codificación posicional y, por lo tanto, debe ir acompañado de los comentarios apropiados.

Este enfoque incluiría componentes que se utilizan en función de la variante que está compilando.

La estructura básica es que su mainclass.cpp incluirá un nuevo archivo llamado MainClassComponents.cpp después de un bloque de declaraciones como la siguiente:

#if VARIANT == 1 # define Uses_Component_1 # define Uses_Component_2 #elif VARIANT == 2 # define Uses_Component_1 # define Uses_Component_3 # define Uses_Component_6 ... #endif #include "MainClassComponents.cpp"

La estructura principal del archivo MainClassComponents.cpp estaría allí para trabajar las dependencias dentro de los subcomponentes de esta manera:

#ifndef _MainClassComponents_cpp #define _MainClassComponents_cpp /* dependencies declarations */ #if defined(Activate_Component_1) #define _REQUIRES_COMPONENT_1 #define _REQUIRES_COMPONENT_3 /* you also need component 3 for component 1 */ #endif #if defined(Activate_Component_2) #define _REQUIRES_COMPONENT_2 #define _REQUIRES_COMPONENT_15 /* you also need component 15 for this component */ #endif /* later on in the header */ #ifdef _REQUIRES_COMPONENT_1 #include "component_1.cpp" #endif #ifdef _REQUIRES_COMPONENT_2 #include "component_2.cpp" #endif #ifdef _REQUIRES_COMPONENT_3 #include "component_3.cpp" #endif #endif /* _MainClassComponents_h */

Y ahora, para cada componente crea un archivo component_xx.cpp.

Por supuesto, estoy usando números pero debes usar algo más lógico basado en tu código.

El uso del preprocesador le permite dividir las cosas sin tener que preocuparse por los cambios de API, lo que es una pesadilla en la producción.

Una vez que haya terminado la producción, puede trabajar en el rediseño.


Rofl, esto me recuerda a mi antiguo trabajo. Parece que, antes de unirme, todo estaba dentro de un archivo enorme (también C ++). Luego lo dividieron (en puntos completamente aleatorios usando incluye) en aproximadamente tres (aún archivos enormes). La calidad de este software fue, como es de esperar, horrible. El proyecto totalizó alrededor de 40k LOC. (No contiene casi ningún comentario pero MUCHO código duplicado)

Al final hice una reescritura completa del proyecto. Comencé por rehacer la peor parte del proyecto desde cero. Por supuesto, tenía en mente una posible (pequeña) interfaz entre esta nueva parte y el resto. Luego inserté esta parte en el viejo proyecto. No modifiqué el código antiguo para crear la interfaz necesaria, sino que simplemente lo reemplacé. Luego di unos pequeños pasos desde allí, reescribiendo el código anterior.

Tengo que decir que esto tomó cerca de medio año y no hubo desarrollo del antiguo código base al lado de las correcciones de errores durante ese tiempo.

editar:

El tamaño se mantuvo en aproximadamente 40k LOC, pero la nueva aplicación contenía muchas más funciones y, presumiblemente, menos errores en su versión inicial que el software de 8 años. Una razón de la reescritura fue también que necesitábamos las nuevas funciones y presentarlas dentro del código antiguo era casi imposible.

El software era para un sistema integrado, una impresora de etiquetas.

Otro punto que debo agregar es que, en teoría, el proyecto fue C ++. Pero no era OO en absoluto, podría haber sido C. La nueva versión estaba orientada a objetos.


Un consejo importante: no mezclar refactorización y correcciones de errores. Lo que desea es una versión de su programa que sea idéntica a la versión anterior, excepto que el código fuente es diferente.

Una forma podría ser comenzar a dividir la función / parte menos grande en su propio archivo y luego incluirla con un encabezado (convirtiendo main.cpp en una lista de #includes, que suena como un olor de código en sí mismo * No estoy aunque un Gurú de C ++), pero al menos ahora está dividido en archivos.

Luego, podría intentar cambiar todas las versiones de mantenimiento al "nuevo" main.cpp o cualquiera que sea su estructura. Nuevamente: no hay otros cambios o correcciones de errores porque rastrearlos es confuso como el infierno.

Otra cosa: por mucho que desee hacer un gran pase para refactorizar todo de una sola vez, puede morder más de lo que puede masticar. Tal vez solo elija una o dos "partes", consígalos en todos los lanzamientos, luego agregue un poco más de valor para su cliente (después de todo, Refactoring no agrega un valor directo por lo que es un costo que debe justificarse) y luego elija otro una o dos partes

Obviamente, eso requiere cierta disciplina en el equipo para usar los archivos divididos y no solo agregar cosas nuevas al main.cpp todo el tiempo, pero nuevamente, tratar de hacer un refactor masivo puede no ser el mejor curso de acción.


Una forma de dividirlo sin demasiado peligro sería echar un vistazo histórico a todos los cambios de línea. ¿Existen ciertas funciones que son más estables que otras? Puntos calientes de cambio si quieres.

Si no se ha cambiado una línea en unos pocos años, probablemente pueda moverla a otro archivo sin demasiada preocupación. Eché un vistazo a la fuente anotada con la última revisión que tocó una línea determinada y veré si hay alguna función que pueda extraer.


Wow, suena genial.Creo que vale la pena intentar explicarle a tu jefe que necesitas mucho tiempo para refactorizar a la bestia. Si él no está de acuerdo, renunciar es una opción.

De todos modos, lo que sugiero es básicamente descartar toda la implementación y reagruparla en nuevos módulos, llamémoslos "servicios globales". El "módulo principal" solo se reenviará a esos servicios y CUALQUIER código nuevo que escriba los usará en lugar del "módulo principal". Esto debería ser factible en un tiempo razonable (debido a que se trata principalmente de copiar y pegar), no rompe el código existente y puede hacerlo una versión de mantenimiento a la vez. Y si aún le queda tiempo, puede gastarlo refactorizando todos los módulos dependientes antiguos para usar también los servicios globales.


"El archivo contiene básicamente la" clase principal "(despacho y coordinación del trabajo interno principal) de nuestro programa, por lo que cada vez que se agrega una característica, también afecta a este archivo y cada vez que crece".

Si ese SWITCH grande (que creo que existe) se convierte en el problema principal de mantenimiento, podría refactorizarlo para que use el diccionario y el patrón de Comando y eliminar toda la lógica del switch del código existente al cargador, que rellena ese mapa, es decir:

// declaration std::map<ID, ICommand*> dispatchTable; ... // populating using some loader dispatchTable[id] = concreteCommand; ... // using dispatchTable[id]->Execute();


Creo que la forma más fácil de rastrear el historial de origen cuando se divide un archivo sería algo como esto:

  1. Haga copias del código fuente original, utilizando los comandos de copia que conservan el historial que proporciona su sistema SCM. Probablemente deba enviarlo en este momento, pero aún no es necesario que le informe a su sistema de compilación sobre los nuevos archivos, por lo que debería estar bien.
  2. Eliminar código de estas copias. Eso no debería romper la historia de las líneas que mantienes.

Creo que lo que haría en esta situación es la bala y:

  1. Averiguar cómo quería dividir el archivo (según la versión de desarrollo actual)
  2. Ponga un bloqueo administrativo en el archivo ("¡¡Nadie toca mainmodule.cpp después de las 5 pm del viernes!"
  3. Pase su largo fin de semana aplicando ese cambio a las> 10 versiones de mantenimiento (de la más antigua a la más reciente), hasta la versión actual.
  4. Elimine mainmodule.cpp de todas las versiones compatibles del software. Es una nueva era: no hay más mainmodule.cpp.
  5. Convenza a la Administración de que no debe admitir más de una versión de mantenimiento del software (al menos sin un contrato de soporte de $ $). Si cada uno de sus clientes tiene su propia versión única ... yeeeeeshhhh. Estaría agregando directivas de compilación en lugar de intentar mantener más de 10 bifurcaciones.

El seguimiento de los cambios anteriores en el archivo se resuelve simplemente con su primer comentario de check-in que dice algo como "split from mainmodule.cpp". Si necesita volver a algo reciente, la mayoría de la gente recordará el cambio, si es dentro de 2 años, el comentario les dirá dónde buscar. Por supuesto, ¿cuán valioso será regresar más de 2 años para ver quién cambió el código y por qué?


He encontrado esta frase como la parte más interesante de tu publicación:

> El archivo se usa y se cambia activamente en varias (> 10) versiones de mantenimiento de nuestro producto, por lo que es muy difícil refactorizarlo

En primer lugar, le recomendaría que utilice un sistema de control de origen para desarrollar estas versiones de mantenimiento 10+ que sean compatibles con la bifurcación.

En segundo lugar, crearía diez sucursales (una para cada una de sus versiones de mantenimiento).

Puedo sentir que ya te encoges! Pero o su control de código fuente no está funcionando para su situación debido a la falta de funciones o no se está utilizando correctamente.

Ahora a la sucursal en la que trabaja: refástrelo como le parezca adecuado, con la certeza de que no molestará a las otras nueve sucursales de su producto.

Me preocuparía un poco que tenga tanto en su función main ().

En cualquier proyecto que escribo, usaría main () solo realiza la inicialización de objetos centrales, como una simulación o un objeto de aplicación, estas clases es donde el trabajo real debería continuar.

También inicializaría un objeto de registro de aplicaciones en main para usarlo globalmente en todo el programa.

Finalmente, en la parte principal también agrego código de detección de fugas en los bloques del preprocesador que aseguran que solo esté habilitado en las versiones DEBUG. Esto es todo lo que añadiría a main (). Main () debe ser corto!

Tu dices eso

> El archivo contiene básicamente la "clase principal" (despacho y coordinación del trabajo interno principal) de nuestro programa

Parece que estas dos tareas podrían dividirse en dos objetos separados: un coordinador y un despachador de trabajo.

Cuando los divide, puede desordenar su "flujo de trabajo SCC", pero parece que adherirse estrictamente a su flujo de trabajo SCC está causando problemas de mantenimiento del software. Olvídalo, ahora y no mires atrás, porque tan pronto como lo arregles, comenzarás a dormir tranquilo.

Si no puede tomar la decisión, pelee con uñas y dientes con su gerente por la misma - su aplicación necesita ser refaccionada - ¡y mal por su sonido! ¡No aceptes un no por respuesta!


Mis 0.05 eurocents:

Rediseñe todo el desorden, divídalo en subsistemas teniendo en cuenta los requisitos técnicos y comerciales (= muchas pistas de mantenimiento paralelas con una base de códigos potencialmente diferente para cada una, obviamente hay una necesidad de alta modificabilidad, etc.).

Al dividir en subsistemas, analice los lugares que más han cambiado y sepárelos de las partes que no cambian. Esto debería mostrarte los puntos problemáticos. Separe las partes más cambiantes en sus propios módulos (por ejemplo, dll) de tal manera que el módulo API se pueda mantener intacto y no tenga que romper la BC todo el tiempo. De esta manera, puede implementar diferentes versiones del módulo para diferentes sucursales de mantenimiento, si es necesario, mientras mantiene el núcleo sin cambios.

Es probable que el rediseño tenga que ser un proyecto independiente, tratar de hacerlo para un objetivo en movimiento no funcionará.

En cuanto al historial del código fuente, mi opinión: olvídalo para el nuevo código. Pero mantenga el historial en algún lugar para que pueda verificarlo, si es necesario. Apuesto a que no lo necesitarás mucho después del comienzo.

Lo más probable es que necesites obtener la participación de la gerencia para este proyecto. Quizás pueda discutir con un tiempo de desarrollo más rápido, menos errores, un mantenimiento más fácil y menos caos general. Algo como "Habilitar de forma proactiva la viabilidad de la garantía de futuro y el mantenimiento de nuestros activos de software críticos" :)

Así es como empezaría a abordar el problema al menos.


Algo que me parece útil hacer (y lo estoy haciendo ahora, aunque no en la escala a la que te enfrentas), es extraer métodos como clases (método de refactorización de objetos). Los métodos que difieren en sus diferentes versiones se convertirán en clases diferentes que se pueden inyectar en una base común para proporcionar el comportamiento diferente que necesita.


Comience agregando comentarios a la misma. Con referencia a dónde se llaman las funciones y si puedes mover las cosas. Esto puede hacer que las cosas se muevan. Realmente necesitas evaluar cuán frágil es el código base. Luego mueva bits comunes de funcionalidad juntos. Pequeños cambios a la vez.


Como lo describió, el problema principal es la diferencia entre la división previa y la división posterior, la combinación de correcciones de errores, etc. Herramienta a su alrededor. No tomará mucho tiempo codificar un script en Perl, Ruby, etc. para eliminar la mayor parte del ruido de diferenciar el pre-split contra una concatenación de post-split. Haz lo que sea más fácil en términos de manejo de ruido:

  • eliminar ciertas líneas antes / durante la concatenación (p. ej., incluir guardas)
  • eliminar otras cosas de la salida de diferencia si es necesario

Incluso podrías hacerlo así que cada vez que hay un registro, se ejecuta la concatenación y tienes algo preparado para diferenciar las versiones de un solo archivo.


Considere formas de reescribir toda la aplicación de una manera más sensata. Tal vez reescriba una pequeña sección como prototipo para ver si su idea es factible.

Si ha identificado una solución viable, refactorice la aplicación en consecuencia.

Si todos los intentos de producir una arquitectura más racional fallan, entonces al menos sabrá que la solución está probablemente en la redefinición de la funcionalidad del programa.


Mis simpatías: en mi trabajo anterior encontré una situación similar con un archivo que era varias veces más grande que el que tiene que tratar. La solución fue:

  1. Escribir código para probar exhaustivamente la función en el programa en cuestión. Suena como que ya no tienes esto en la mano ...
  2. Identifique algún código que pueda resumirse en una clase de ayudante / utilidades. No es necesario que sea grande, solo algo que no es realmente parte de su clase "principal".
  3. Refactoriza el código identificado en 2. en una clase separada.
  4. Vuelva a ejecutar sus pruebas para asegurarse de que nada se rompió.
  5. Cuando tenga tiempo, vaya a 2. y repita según sea necesario para que el código sea manejable.

Las clases que construyas en el paso 3. es probable que las iteraciones crezcan para absorber más código que sea apropiado para su nueva función clara.

También podría añadir:

0: compre http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 sobre el trabajo con código heredado

Desafortunadamente, este tipo de trabajo es demasiado común, pero mi experiencia es que hay un gran valor en poder hacer que el código de trabajo, pero horrible, sea cada vez menos horrible mientras lo mantengo funcionando


Otro libro que puede encontrar interesante / útil es la Refactoring .