¿Qué hacer con un file fuente de C ++ de 11000 líneas?

Así que tenemos este gran file de código fuente mainmodule.cpp (es 11000 líneas de gran tamaño) en nuestro proyecto y cada vez que tengo que tocarlo me estremezco.

Como este file es tan central y grande, sigue acumulando más y más código y no puedo pensar en una buena manera de que realmente comience a networkingucirse.

El file se usa y se modifica activamente en varias (> 10) versiones de mantenimiento de nuestro producto, por lo que es muy difícil refactorizarlo. Si tuviera que "simplemente" dividirlo, digamos para empezar, en 3 files, entonces la fusión de los cambios de las versiones de mantenimiento se convertirá en una pesadilla. Y también si divide un file con un historial tan largo y rico, rastrear y verificar los cambios anteriores en el historial de SCC repente se vuelve mucho más difícil.

El file básicamente contiene la "class principal" (distribución y coordinación principal del trabajo interno) de nuestro progtwig, por lo que cada vez que se agrega una function, también afecta este file y cada vez que crece. 🙁

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

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

  1. Busque algún código en el file que sea relativamente estable (que no cambie rápidamente y que no varíe mucho entre las twigs) y que pueda mantenerse como una unidad independiente. Mover esto a su propio file, y para el caso en su propia class, en todas las twigs. Debido a que es estable, esto no provocará (muchas) fusiones "incómodas" que se deben aplicar a un file diferente del que se crearon originalmente, cuando se fusiona el cambio de una twig a otra. Repetir.

  2. Encuentre un código en el file que básicamente solo se aplica a una pequeña cantidad de sucursales, y podría ser independiente. No importa si cambia rápidamente o no, debido al pequeño número de sucursales. Mueva esto a sus propias classs y files. Repetir.

Entonces, nos deshacemos del código que es igual en todas partes, y el código que es específico para ciertas twigs.

Esto te deja con un núcleo de código mal administrado: se necesita en todas partes, pero es diferente en cada twig (y / o cambia constantemente para que algunas twigs se ejecuten detrás de otras), y sin embargo está en un solo file que estás intentando sin éxito fusionar entre twigs. Para de hacer eso. Branch el file de forma permanente , tal vez mediante el cambio de nombre en cada twig. Ya no es "principal", es "principal para la configuration X". De acuerdo, entonces pierde la capacidad de aplicar el mismo cambio a varias twigs al fusionarse, pero este es en cualquier 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 aplicarlos de forma independiente en cada twig.

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, "fusionarse 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 probablemente sea irrelevante.

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

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

Si no puedes dejar de codificar durante el process de refactorización, puedes dejar este gran file como por un time al less sin agregarle más código: dado que contiene una "class principal", puedes henetworkingar de ella y mantener la class henetworkingada ( es) con funciones sobrecargadas en varios files nuevos pequeños y bien diseñados.

Me parece que estás enfrentando una serie de olores de código aquí. En primer lugar, la class 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 necesario.

Si bien puedo entender sus inquietudes con respecto a la trazabilidad luego de una refactorización, esperaría que esta class sea bastante difícil de mantener y mejorar, y que cualquier cambio que realice probablemente cause efectos secundarios. Supongo que el costo de estos supera el costo de la refacturación de la class.

En cualquier caso, dado que el código huele solo empeorará con el time, al less en algún momento el costo de estos será mayor que el costo de refactorización. Según su descripción, asumiría que ha pasado el punto de inflexión.

Refactorizar esto debe hacerse en pequeños pasos. Si es posible, agregue testings automatizadas para verificar el comportamiento actual antes de refaccionar cualquier cosa. A continuación, select áreas pequeñas de funcionalidad aislada y extráigalas como types para delegar la responsabilidad.

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

La única solución que he imaginado para 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.

Inserta una nueva class cpp por encima de la class principal original. Por ahora, networkingirigiría básicamente todas las llamadas a la class principal actual, pero apunta a hacer que la API de esta nueva class sea lo más clara y concisa posible.

Una vez hecho esto, tendrá la posibilidad de agregar nuevas funcionalidades en nuevas classs.

En cuanto a las funcionalidades existentes, tienes que moverlas progresivamente en nuevas classs a medida que se vuelven lo suficientemente estables. Perderá la ayuda de SCC por este fragment de código, pero no se puede hacer mucho al respecto. Solo elige el momento adecuado.

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

Información Adicional

Tenga en count que Git es un SCC que puede seguir fragments de código de un file a otro. He escuchado cosas buenas al respecto, por lo que podría ser útil mientras mueve progresivamente su trabajo.

Git se construye alnetworkingedor de la noción de blobs que, si lo entiendo correctamente, representan fragments de files de código. Mueva estas piezas en diferentes files y Git las encontrará, incluso si las modifica. Además del video de Linus Torvalds mencionado en los comentarios a continuación, no he podido encontrar algo claro al respecto.

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

Déjame adivinar: ¿Diez clientes con sets 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.

Usted reconoce que tener un file enorme es un problema, pero aún más problemas son diez versiones que debe mantener "actualizadas". Eso es mantenimiento múltiple. SCC puede hacer eso más fácil, pero no puede hacerlo bien.

Antes de intentar dividir el file en partes, debe volver a sincronizar las diez twigs para que pueda ver y dar forma a todo el código a la vez. Puede hacer esto una twig a la vez, probando ambas twigs contra el mismo file de código principal. Para hacer cumplir el comportamiento personalizado, puede usar #ifdef y sus amigos, pero es mejor tanto como sea posible usar if / else ordinario contra las constantes definidas. De esta forma, su comstackdor verificará todos los types y muy probablemente elimine el código de object "muerto" de todos modos. (Es posible que desee desactivar la advertencia sobre el código muerto, sin embargo).

Una vez que hay una sola versión de ese file compartida implícitamente por todas las twigs, entonces es bastante más fácil comenzar los methods tradicionales de refactorización.

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

A corto ploop, el file parecerá crecer. Esto esta bien. Lo que estás haciendo es unir las cosas que necesitan estar juntas. Luego, comenzará a ver áreas que son claramente las mismas independientemente de la versión; estos pueden ser dejados solos o refactorizados a voluntad. Otras áreas diferirán claramente según la versión. Usted tiene una serie de opciones en este caso. Un método es delegar las diferencias a los objects de estrategia por versión. Otra es derivar versiones de cliente de una class abstracta común. Pero ninguna de estas transformaciones es posible siempre y cuando tengas diez "consejos" de desarrollo en diferentes twigs.

No sé si esto resuelve tu problema, pero lo que supongo que quieres hacer es migrar el contenido del file a files más pequeños, independientes entre sí (resumidos). Lo que también me llega es que tienes alnetworkingedor de 10 versiones diferentes del software flotando y necesitas darles soporte sin estropear las cosas.

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

Creo que solo tienes estas opciones:

  1. No migres y quédate con lo que tienes. Posiblemente renuncie a su trabajo y comience a trabajar en software serio con un buen layout además. La progtwigción extrema no siempre es la mejor solución si está trabajando en un proyecto a largo ploop con backgrounds suficientes para sobrevivir a uno o dos lockings.

  2. Elabore un layout de cómo le gustaría que se vea su file una vez que se haya dividido. Cree los files necesarios e integrelos en su aplicación. Cambia el nombre de las funciones o sobrecargalas para tomar un parámetro adicional (¿tal vez solo un boolean simple?). Una vez que tenga que trabajar en su código, migre las funciones que necesita para trabajar al nuevo file y asigne las funciones de las funciones anteriores a las nuevas funciones. Debería seguir teniendo su file principal de esta manera, y aún así poder ver los cambios que se le hicieron, una vez que se trata de una function específica, usted sabe exactamente cuándo fue subcontratado, 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 reescribir algunas partes de la aplicación para poder hacer negocios serios.

Exactamente este problema se maneja en uno de los capítulos del libro "Trabajar eficazmente con código henetworkingado" ( http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 ).

Creo que lo mejor sería crear un set de classs de command que se correlacionen con los puntos API de mainmodule.cpp.

Una vez que estén en su lugar, deberá refactorizar la base de códigos existente para acceder a estos puntos API a través de las classs de command, una vez hecho esto, podrá refactorizar la implementación de cada command en una nueva estructura de classs.

Por supuesto, con una sola class de 11 KLOC, el código que hay probablemente esté muy acoplado y sea frágil, pero la creación de classs de command individuales ayudará mucho más que cualquier otra estrategia de proxy / fachada.

No envidio la tarea, pero a medida que pasa el time, 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 classs de Comando diferentes sobre una Fachada (relativamente) monolítica. El mapeo de una sola Fachada en un file de 11 KLOC probablemente tendrá que dividirse en algunos grupos diferentes.

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

Por supuesto, ambas opciones son mejores que el file único 11 KLOC y creciente.

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

Una forma podría ser comenzar dividiendo la parte / function less grande en su propio file y luego includela con un encabezado (convirtiendo así main.cpp en una list de #includes, que suena un código huele en sí mismo * No estoy sin embargo, un gurú de C ++), pero al less ahora está dividido en files).

A continuación, puede tratar de cambiar todas las versiones de mantenimiento a la "nueva" main.cpp o cualquiera que sea su estructura. De nuevo: no hay otros cambios o correcciones de errores porque rastrearlos es confuso como el infierno.

Otra cosa: por mucho que desee hacer un gran paso para refaccionar todo el asunto de una vez, puede morder más de lo que puede masticar. Tal vez solo elija una o dos "partes", consíguelas en todos los lanzamientos, luego agregue un poco más de valor para su cliente (después de todo, la refabricación no agrega valor directo por lo que es un costo que tiene que justificarse) y luego elija otro una o dos partes.

Obviamente, eso requiere cierta disciplina en el equipo para usar los files divididos y no solo agregar cosas nuevas al main.cpp todo el time, pero una vez más, tratar de hacer un refactor masivo puede no ser el mejor curso de acción.

Rofl, esto me restring mi viejo trabajo. Parece que, antes de unirme, todo estaba dentro de un file enorme (también C ++). Luego lo han dividido (en puntos completamente random usando inclusiones) en aproximadamente tres (files aún grandes). La calidad de este software era, como era de esperar, horrible. El proyecto ascendió a aproximadamente 40k LOC. (que no contiene casi ningún comentario pero MUCHA cantidad de código duplicado)

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

Tengo que decir que esto tomó alnetworkingedor de medio año y no hubo desarrollo de la antigua base de código junto a las correcciones de errores durante ese time.


editar:

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

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

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

De acuerdo, la mayor parte de la reescritura de la API del código de producción es una mala idea como inicio. 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 file.

En segundo lugar, debe tomar esta versión de producción y crear una sucursal que administre las construcciones utilizando directivas de preprocesamiento para dividir el file grande. La split de la compilation con las directivas del preprocesador JUST (#ifdefs, #includes, #endifs) es más fácil que la reencoding de la API. Definitivamente es más fácil para sus SLAs y soporte continuo.

Aquí podría simplemente cortar las funciones que se relacionan con un subsistema particular dentro de la class y colocarlas en un file que diga mainloop_foostuff.cpp e includelo en mainloop.cpp en la location correcta.

O

Una manera más lenta pero robusta sería diseñar una estructura de dependencies internas con doble indirección en cómo se incluyen las cosas. Esto te permitirá dividir las cosas y aún así cuidar las codependencies. Tenga en count que este enfoque requiere encoding posicional y, por lo tanto, debe combinarse con los comentarios apropiados.

Este enfoque includeía componentes que se usan según la variante que está comstackndo.

La estructura básica es que su mainclass.cpp includeá un nuevo file llamado MainClassComponents.cpp después de un bloque de declaraciones como las siguientes:

 #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 primaria del file MainClassComponents.cpp estaría allí para resolver las dependencies dentro de los subcomponentes como este:

 #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 file component_xx.cpp.

Por supuesto que estoy usando numbers, pero debería usar algo más lógico basado en su código.

Usar el preprocesador te permite dividir las cosas sin tener que preocuparte por los cambios de la API, lo cual es una pesadilla en la producción.

Una vez que hayas establecido la producción, podrás trabajar en el networkingiseño.

Bueno, entiendo tu dolor 🙂 También he estado en algunos de esos 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 arguments, condiciones previas y posteriores en methods, y eventualmente agregar testings unitarias para capturar la funcionalidad actual de las fonts. Una vez que tenga esto, estará mejor equipado para volver a factorizar el código porque habrá afirmado y aparecerán errores que lo alertarán si ha olvidado algo.

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

No debe preocuparse por networkingucir el tamaño del file, sino más bien por networkingucir el tamaño de la class. Se trata de casi lo mismo, pero te hace ver el problema desde un ángulo diferente (como sugiere @Brian Rasmussen, tu class parece tener muchas responsabilidades).

Lo que tienes es un ejemplo clásico de antipatrón de layout conocido llamado blob . Tómese su time para leer el artículo que menciono aquí, y tal vez pueda encontrar algo útil. Además, si este proyecto es tan grande como parece, debes considerar algún layout para evitar que crezca el código que no puedes controlar.

Esta no es una respuesta al gran problema, sino una solución teórica para una parte específica de ella:

  • Averigua dónde quieres dividir el file grande en subfiles. Ponga los comentarios en algún formatting especial en cada uno de esos puntos.

  • Escriba un script bastante trivial que dividirá el file en subfiles en esos puntos. (Tal vez los comentarios especiales tengan nombres de file embeddeds que el script puede usar como instrucciones para dividirlo). Debería conservar los comentarios como parte de la split.

  • Ejecute el script Eliminar el file original.

  • Cuando necesite fusionarse desde una bifurcación, primero vuelva a crear el file grande volviendo a concatenar las piezas, realice la fusión y luego vuelva a dividirla.

Además, si desea conservar el historial del file SCC, espero que la mejor manera de hacerlo sea decirle a su sistema de control de origen que los files pieza individuales son copys del original. Luego preservará el historial de las secciones que se saveon en ese file, aunque por supuesto también registrará que las partes grandes fueron "eliminadas".

Una forma de dividirlo sin demasiado peligro sería analizar históricamente todos los cambios de línea. ¿Hay ciertas funciones que son más estables que otras? Puntos calientes de cambio si se quiere.

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

Wow, suena genial. I think explaining to your boss, that you need a lot of time to refactor the beast is worth a try. If he doesn't agree, quitting is an option.

Anyway, what I suggest is basically throwing out all the implementation and regrouping it into new modules, let's call those "global services". The "main module" would only forward to those services and ANY new code you write will use them instead of the "main module". This should be feasible in a reasonable amount of time (because it's mostly copy and paste), you don't break existing code and you can do it one maintenance version at a time. And if you still have any time left, you can spend it refactoring all old depending modules to also use the global services.

My sympathies – in my previous job I encountenetworking a similar situation with a file that was several times larger than the one you have to deal with. Solution was:

  1. Write code to exhaustively test the function in the program in question. Sounds like you won't already have this in hand…
  2. Identify some code that can be abstracted out into a helper/utilities class. Need not be big, just something that is not truly part of your 'main' class.
  3. Refactor the code identified in 2. into a separate class.
  4. Rerun your tests to ensure nothing got broken.
  5. When you have time, goto 2. and repeat as requinetworking to make the code manageable.

The classes you build in step 3. iterations will likely grow to absorb more code that is appropriate to their newly-clear function.

I could also add:

0: buy Michael Feathers' book on working with legacy code

Unfortunately this type of work is all too common, but my experience is that there is great value in being able to make working but horrid code incrementally less horrid while keeping it working.

Consider ways to rewrite the entire application in a more sensible way. Maybe rewrite a small section of it as a prototype to see if your idea is feasible.

If you've identified a workable solution, refactor the application accordingly.

If all attempts to produce a more rational architecture fail, then at least you know the solution is probably in networkingefining the program's functionality.

My 0.05 eurocents:

Re-design the whole mess, split it into subsystems taking into account the technical and business requirements (=many parallel maintenance tracks with potentially different codebase for each, there is obviously a need for high modifiability, etc.).

When splitting into subsystems, analyze the places which have most changed and separate those from the unchanging parts. This should show you the trouble-spots. Separate the most changing parts to their own modules (eg dll) in such a way that the module API can be kept intact and you don't need to break BC all the time. This way you can deploy different versions of the module for different maintenance branches, if needed, while having the core unchanged.

The networkingesign will likely need to be a separate project, trying to do it to a moving target will not work.

As for the source code history, my opinion: forget it for the new code. But keep the history somewhere so you can check it, if needed. I bet you won't need it that much after the beginning.

You most likely need to get management buy-in for this project. You can argue perhaps with faster development time, less bugs, easier maintaining and less overall chaos. Something along the lines of "Proactively enable the future-proofness and maintenance viability of our critical software assets" 🙂

This is how I'd start to tackle the problem at least.

Start by adding comments to it. With reference to where functions are called and if you can move things around. This can get things moving. You really need to assess how brittle the code base it. Then move common bits of functionality together. Small changes at a time.

Another book you may find interesting/helpful is Refactoring .

Something I find useful to do (and I'm doing it now although not at the scale you face), is to extract methods as classes (method object refactoring). The methods that differ across your different versions will become different classes which can be injected into a common base to provide the different behaviour you need.

I found this sentence to be the most interesting part of your post:

> The file is used and actively changed in several (> 10) maintenance versions of our product and so it is really hard to refactor it

First, I would recommend that you use a source control system for developing these 10 + maintenance versions that supports branching.

Second, I would create ten branches (one for each of your maintenance versions).

I can feel you cringing already! But either your source control isn't working for your situation because of a lack of features, or it's not being used correctly.

Now to the branch you work on – refactor it as you see fit, safe in the knowledge that you'll not upset the other nine branches of your product.

I would be a bit concerned that you have so much in your main() function.

In any projects I write, I would use main() only perform initialization of core objects – like a simulation or application object – these classes is where the real work should go on.

I would also initialize an application logging object in main for use globally throughout the program.

Finally, in main I also add leak detection code in preprocessor blocks that ensure it's only enabled in DEBUG builds. This is all I would add to main(). Main() should be short!

You say that

> The file basically contains the "main class" (main internal work dispatching and coordination) of our program

It sounds like these two tasks could be split into two separate objects – a co-ordinator and a work dispatcher.

When you split these up, you may mess up your "SCC workflow", but it sounds like adhering stringently to your SCC workflow is causing software maintenance problems. Ditch it, now and don't look back, because as soon as you fix it, you'll begin to sleep easy.

If you're not able to make the decision, fight tooth and nail with your manager for it – your application needs to be refactonetworking – and badly by the sounds of it! Don't take no for an answer!

As you've described it, the main issue is diffing pre-split vs post-split, merging in bug fixes etc.. Tool around it. It won't take that long to hardcode a script in Perl, Ruby, etc. to rip out most of the noise from diffing pre-split against a concatenation of post-split. Do whatever's easiest in terms of handling noise:

  • remove certain lines pre/during concatenation (eg include guards)
  • remove other stuff from the diff output if necessary

You could even make it so whenever there's a checkin, the concatenation runs and you've got something prepanetworking to diff against the single-file versions.

  1. Do not ever touch this file and the code again!
  2. Treat is like something you are stuck with. Start writing adapters for the functionality encoded there.
  3. Write new code in different units and talk only to adapters which encapsulate the functionality of the monster.
  4. … if only one of the above is not possible, quit the job and get you a new one.

"The file basically contains the "main class" (main internal work dispatching and coordination) of our program, so every time a feature is added, it also affects this file and every time it grows."

If that big SWITCH (which I think there is) becomes the main maintenance problem, you could refactor it to use dictionary and the Command pattern and remove all switch logic from the existing code to the loader, which populates that map, ie:

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

I think the easiest way to track the history of source when splitting a file would be something like this:

  1. Make copies of the original source code, using whatever history-preserving copy commands your SCM system provides. You'll probably need to submit at this point, but there's no need yet to tell your build system about the new files, so that should be ok.
  2. Delete code from these copies. That should not break the history for the lines you keep.

I think what I would do in this situation is bit the bullet and:

  1. Figure out how I wanted to split the file up (based on the current development version)
  2. Put an administrative lock on the file ("Nobody touch mainmodule.cpp after 5pm Friday!!!"
  3. Spend your long weekend applying that change to the >10 maintenance versions (from oldest to newest), up to and including the current version.
  4. Delete mainmodule.cpp from all supported versions of the software. It's a new Age – there is no more mainmodule.cpp.
  5. Convince Management that you shouldn't be supporting more than one maintenance version of the software (at least without a big $$$ support contract). If each of your customers have their own unique version…. yeeeeeshhhh. I'd be adding compiler directives rather than trying to maintain 10+ forks.

Tracking old changes to the file is simply solved by your first check-in comment saying something like "split from mainmodule.cpp". If you need to go back to something recent, most people will remember the change, if it's 2 year from now, the comment will tell them where to look. Of course, how valuable will it be to go back more than 2 years to look at who changed the code and why?

    Intereting Posts