c++ – Preprocesador – Duda existencial – c++ c git

Pregunta:


realmente tengo una duda existencia, y a medida que avanzo me hace mas ruido y realmente no sé si estoy haciendo las cosas bien. Tengo un software propio, que he desarrollado hace unos meses atras…va bastante bien en su finalidad, pero resulta que me han pedido otro que tiene la funcionalidad del anterior pero que agrega funcionalidades adicionales…osea, el primero se basa en el segundo.
Lo que a mí se me ocurrió, es sobre el juego de fuentes, definir una constante a nivel del proyecto y agregar sentencias del preprocesador…en principio se me ocurrió que era lo mejor…de esa manera solo cambiaría en lugares claves solo cuando necesito clases nuevas de la nueva funcionalidad…PERO a medida que avanzo veo que se complica y el código se vuelve in-mantenible. Muestro partes para que vean que me esta sucediendo.

#include "enginegraph.h"
#include "trackfarmgraph.h"
#ifdef __CORTE_SECCION__
#include "multiworkerdpathgraph.h"
#else
#include "workedpathgraph.h"
#endif
#include "pointsfarmgraph.h"

…. en otras partes del proyecto …

pointsGraph = NULL;
#ifndef __CORTE_SECCION__
    worker = NULL;
#else
    multiWorker = NULL; 
#endif
engine = NULL;
dlg = NULL;

…. en otras partes del proyecto …

#ifndef __CORTE_SECCION__
if (worker)
{
    disconnect(worker);
    delete worker;
    worker = NULL;
}
#else
if (multiWorker)
{
    disconnect(multiWorker);
    delete multiWorker;
    multiWorker = NULL;
}
#endif

if (track)
{
    disconnect(track);
    delete track;
    track = NULL;
}

Con esto voy notando que el código se torna ilegible y con el tiempo lo será mas.
La ventaja que encuentro es que si aparecen bugs en la parte comun, se corrigen y ambos software quedan OK…pero el costo de ilegibilidad y mantenimiento creo que crece exponencialmente.
De que otra forma se puede hacer esto?. GRACIAS

Preguntado por: Emiliano Torres

Neoniet

Te voy a poner un ejemplo sencillo.

Iniciamos un repositorio

git init

Añadimos los archivos iniciales del programa

git add main.cpp Clase1.cpp Clase1.hpp

git commit -m “Programa inicial”

Un cliente nos pide una cosa muy especial que probablemente no le valdrá al resto. El cliente tiquismiquis!!
Pues nos creamos una rama

git checkout -b clientetiquismiquis

Añadimos los nuevos archivos y las modificaciones de los antiguos

git add *

git commit -m “Versión para cliente tiquismiquis lista”

Ahora saltamos a nuestra versión principal para seguir mejorando el programa

git checkout master

Añadimos las mejoras a la rama principal

git add *

git commit -m “Mejoras añadidas”

Y ahora queremos mejorar la versión del cliente tiquismiquis, pues nos vamos a su rama:

git checkout clientetiquismiquis

y fusionamos con master

git merge master

Auto-merging main.cpp
CONFLICT (content): Merge conflict in main.cpp
Automatic merge failed; fix conflicts and then commit the result.

Leches!!! resulta que no puede fusionarlas automáticamente, pues abrimos el archivo en conflicto, lo dejamos bien a mano y seguimos

git add main.cpp

git commit -m “Fusionadas mejoras de master en clientetiquismiquis”

Todo listo 🙂

El problema en este caso es que no estás aislando correctamente las diferentes funciones.

Las directivas del preprocesador pueden aportar mucha flexibilidad al código… peeero distan mucho de ser perfectas:

  • Cuantos más símbolos defines más complicado es mantener el código
  • El código que queda fuera de un #ifdef no se compila, lo que te obliga a realizar varias compilaciones para probar todas las combinaciones (mantenimiento demasiado caro)
  • Los símbolos no tienen tipado, es facil confundirse y poner _CORTE_SECCION__ en vez de __CORTE_SECCION__… y entonces gasta horas en identificar el problema.

Lo recomendable es aislar las diferentes funcionalidades en clases o funciones. Con los nuevos estándares usar plantillas es una opción bastante asequible.

Me explico. Partamos de este fragmento de código:

#ifndef __CORTE_SECCION__
    worker = NULL;
#else
    multiWorker = NULL; 
#endif

// ...

#ifndef __CORTE_SECCION__
if (worker)
{
    disconnect(worker);
    delete worker;
    worker = NULL;
}
#else
if (multiWorker)
{
    disconnect(multiWorker);
    delete multiWorker;
    multiWorker = NULL;
}
#endif

Mejorarlo y aislar el código aquí es relativamente sencillo.

Lo primero que haría sería definir una constante en base a __CORTE_SECCION__:

#ifdef __CORTE_SECCION__
  constexpr bool IsMultiWorker = false;
#else
  constexpr bool IsMultiWorker = true;
#endif

A continuación podemos crear una colección de plantillas para los diferentes casos de uso (dos en este caso). Nota que no has indicado el tipo de worker y multiworker. Asumiré que son Worker y Multiworker.

El caso, nos interesa crear una herramienta que nos permita crear un Worker o un Multiworker según las circunstancias:

template<bool>
struct MultiWorkerTraits;

template<>
struct MultiWorkerTraits<false>
{
  using type = Worker;
};

template<>
struct MultiWorkerTraits<true>
{
  using type = MultiWorker;
};

Y seguidamente, las funciones de desconexión. Como la lógica a ejecutar es la misma nos basta con una única plantilla:

template<class T>
void Disconnect(T & worker)
{
  if( worker )
  {
    disconnect(worker);
    delete worker;
    worker = nullptr;
  }
}

Ya tenemos todos los mimbres listos, ya solo falta acometer cambios en el fragmento original de código:

typename MultiWorkerTraits<IsMultiWorker>::type * worker = nullptr;

// ...

Disconnect(worker);

Fuente

Add a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *