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

11 minutos de lectura

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

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

El archivo se usa y cambia 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 archivos, fusionar los cambios de las versiones de mantenimiento se convertiría en una pesadilla. Y también si divide un archivo con un historial tan largo y rico, rastreando y verificando cambios antiguos en el SCC la historia de repente se vuelve mucho más difícil.

El archivo contiene básicamente la “clase principal” (despacho y coordinación de trabajo interno principal) de nuestro programa, por lo que cada vez que se agrega una función, 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 de origen separado sin estropear el SCC flujo de trabajo?

(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 la comparación real y la fusión de archivos)

  • @BoltClock: En realidad, Vim lo abrirá bastante rápido.

    – ereOn

    1 de septiembre de 2010 a las 7:49

  • 69305 líneas y contando. Un archivo en nuestra aplicación en el que mi colega vuelca la mayor parte de su código. No pude resistirme a publicar esto aquí. No tengo a nadie en mi empresa a quien informarle de esto.

    – Agnel Kurian

    1 de septiembre de 2010 a las 7:55


  • no lo entiendo ¿Cómo puede el comentario “renunciar a ese trabajo” obtener tantos votos a favor? Algunas personas parecen vivir en un país de hadas, donde todos los proyectos se escriben desde cero y/o usan 100% Agile, TDD, … (ponga cualquiera de sus palabras de moda aquí).

    – Stefan

    1 de septiembre de 2010 a las 14:11

  • @Stefan: Cuando me enfrenté a una base de código similar, hice exactamente eso. No me apetecía pasar el 95% de mi tiempo trabajando alrededor la basura en un código base de 10 años, y el 5% realmente escribe código. en realidad fue imposible para probar algunos aspectos del sistema (y no me refiero a la prueba unitaria, me refiero a ejecutar el código para ver si funcionaba). No duré mi período de prueba de 6 meses, me cansé de pelear batallas perdidas y escribir código que no podía soportar.

    – Preocupado binario

    1 de septiembre de 2010 a las 15:58

  • con respecto al aspecto de seguimiento del historial de dividir el archivo: use el comando de copia de su sistema de control de versiones para copiar el archivo completo tantas veces como desee dividirlo, y luego elimine todo el código de cada una de las copias que no desea en ese archivo. Esto preserva el historial general, ya que cada uno de los archivos divididos puede rastrear su historial a través de la división (que se verá como una eliminación gigante de la mayoría de los contenidos del archivo).

    – rmeador

    1 de septiembre de 2010 a las 18:20

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

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

Por lo tanto, nos deshicimos del código que es el mismo en todas partes y del código que es específico para ciertas ramas.

Esto lo 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, de modo que algunas ramas se ejecutan detrás de otras) y, sin embargo, está en un solo archivo que está tratando sin éxito de fusionarse entre ramas. Para de hacer eso. Bifurcar el archivo permanentemente, quizás renombrándolo en cada rama. Ya no es “principal”, es “principal para la configuración X”. De acuerdo, pierde la capacidad de aplicar el mismo cambio a varias ramas al fusionarse, pero en cualquier caso, este es 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 lidiar con los conflictos, entonces 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 capacidades de fusión de git son probablemente mejores que la herramienta de fusión que estás usando. Entonces, el problema central, “la fusión 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.

  • En cuanto a la fusión: he mirado GIT y he mirado SVN y he mirado Perforce y déjame decirte que nada de lo que he visto en ninguna parte supera a AccuRev+Araxis en lo que hacemos. 🙂 (Aunque GIT puede hacer esto [ stackoverflow.com/questions/1728922/… ] y AccuRev no puede; cada uno tiene que decidir por sí mismo si esto es parte de la fusión o del análisis de la historia).

    – Martín Ba

    1 de septiembre de 2010 a las 13:25

  • Bastante justo, tal vez ya tenga la mejor herramienta disponible. La capacidad de Git para fusionar un cambio que ocurrió en el Archivo A en la rama X, en el Archivo B en la rama Y, debería facilitar la división de archivos ramificados, pero presumiblemente el sistema que usa tiene ventajas que le gustan. De todos modos, no estoy proponiendo que cambies a git, solo digo que SCC marca la diferencia aquí, pero aun así estoy de acuerdo contigo en que esto se puede descontar 🙂

    –Steve Jessop

    1 de septiembre de 2010 a las 13:55


  • Huele mucho: huele a que el antipatrón Blob está en la casa… en.wikipedia.org/wiki/God_object. Su comida favorita es el código de espagueti: en.wikipedia.org/wiki/Spaghetti_code 🙂

    – jdehaan

    1 de septiembre de 2010 a las 10:42

  • @jdehaan: Estaba tratando de ser diplomático al respecto 🙂

    – Brian Rasmussen

    1 de septiembre de 2010 a las 11:03

  • +1 De mí también, no me atrevo a tocar ni siquiera el código complejo que he escrito sin pruebas para cubrirlo.

    – Danny Thomas

    2 de septiembre de 2010 a las 12:47

La única solución que he imaginado para tales problemas es la siguiente. 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 cpp encima de la clase principal original. Por ahora, básicamente redirigiría todas las llamadas a la clase principal actual, pero su objetivo es hacer que la API de esta nueva clase sea lo más clara y concisa posible.

Una vez hecho esto, tienes la posibilidad de añadir nuevas funcionalidades en nuevas clases.

En cuanto a las funcionalidades existentes, debe moverlas progresivamente en nuevas clases a medida que se vuelven lo suficientemente estables. Perderá la ayuda de SCC para este fragmento 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 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 en su trabajo.

Git se construye en torno a la noción de blobs que, si entiendo correctamente, representan piezas de archivos de código. Mueva estas piezas en diferentes archivos y Git las encontrará, incluso si las modifica. Aparte de el vídeo de Linus Torvalds mencionado en los comentarios a continuación, no he podido encontrar algo claro al respecto.

  • una referencia sobre cómo GIT hace eso / cómo lo haces con GIT sería muy bienvenido.

    – Martín Ba

    1 de septiembre de 2010 a las 8:05

  • @Martin Git lo hace automáticamente.

    – Mateo

    1 de septiembre de 2010 a las 8:19

  • @Martin: Git lo hace automáticamente, porque no rastrea archivos, rastrea contenido. En realidad, es más difícil en git simplemente “obtener el historial de un archivo”.

    – Arafangión

    1 de septiembre de 2010 a las 8:49

  • @Martín youtube.com/watch?v=4XpnKHJAok8 es una charla donde Torvalds habla de git. Lo menciona más adelante en la charla.

    – Mateo

    1 de septiembre de 2010 a las 9:11

  • @Martin, mira esta pregunta: stackoverflow.com/questions/1728922/…

    – Benjol

    1 de septiembre de 2010 a las 11:16

Confucio dice: “el primer paso para salir del hoyo es dejar de cavar hoyos”.

  • una referencia sobre cómo GIT hace eso / cómo lo haces con GIT sería muy bienvenido.

    – Martín Ba

    1 de septiembre de 2010 a las 8:05

  • @Martin Git lo hace automáticamente.

    – Mateo

    1 de septiembre de 2010 a las 8:19

  • @Martin: Git lo hace automáticamente, porque no rastrea archivos, rastrea contenido. En realidad, es más difícil en git simplemente “obtener el historial de un archivo”.

    – Arafangión

    1 de septiembre de 2010 a las 8:49

  • @Martín youtube.com/watch?v=4XpnKHJAok8 es una charla donde Torvalds habla de git. Lo menciona más adelante en la charla.

    – Mateo

    1 de septiembre de 2010 a las 9:11

  • @Martin, mira esta pregunta: stackoverflow.com/questions/1728922/…

    – Benjol

    1 de septiembre de 2010 a las 11:16

Déjame adivinar: ¿Diez clientes con conjuntos de funciones 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 archivo enorme es un problema, pero aún más problema son las diez versiones que tiene que 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 archivo en partes, debe volver a sincronizar las diez ramas entre sí para que pueda ver y dar forma a 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 hacer cumplir el comportamiento personalizado, puede usar #ifdef y amigos, pero es mejor en la medida de lo posible usar if/else normal contra constantes definidas. De esta manera, su compilador verificará todos los tipos y lo más probable es que elimine el código de 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 sucursales, 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 sucursales, pero no se vuelvan 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 juntar cosas que necesitan estar juntas. Posteriormente, comenzará a ver áreas que son claramente iguales independientemente de la versión; estos pueden dejarse solos o refactorizarse a voluntad. Otras áreas diferirán claramente dependiendo de la versión. Tienes varias opciones en este caso. Un método es delegar las diferencias a objetos de estrategia por versión. Otra es derivar versiones de clientes de una clase abstracta común. Pero ninguna de estas transformaciones es posible mientras tengas diez “puntas” de desarrollo en diferentes ramas.

  • Estoy de acuerdo en que el objetivo debería ser tener una versión del software, pero ¿no sería mejor usar archivos de configuración (tiempo de ejecución) y no compilar la personalización del tiempo?

    – Esben Skov Pedersen

    2 de septiembre de 2010 a las 8:20

  • O incluso “clases de configuración” para la compilación de cada cliente.

    – tc.

    2 de septiembre de 2010 a las 12:51

  • Me imagino que la configuración en tiempo de compilación o en tiempo de ejecución es funcionalmente irrelevante, pero no quiero limitar las posibilidades. La configuración en tiempo de compilación tiene la ventaja de que el cliente no puede piratear un archivo de configuración para activar características adicionales, ya que coloca toda la configuración en el árbol fuente en lugar de un código de “objeto textual” desplegable. La otra cara es que tiendes hacia AlternateHardAndSoftLayers si es tiempo de ejecución.

    – Ian

    3 de septiembre de 2010 a las 1:56

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad