2011-12-14 22 views
10

Trabajamos con un sistema heredado muy antiguo implementado en C++ con el compilador VC6. Ahora estamos en el proceso de refactorizar el código. También cambiamos al compilador VC9.¿Cómo implementar una gran cantidad de envoltorios complejos para API/framework heredado (C++ Macros vs. C++ Templates vs. Code generator)?

Usamos un marco de propiedad externo, que también es código heredado y no es comprobable por unidad. Con el fin de hacer que nuestra unidad de código comprobable, introdujimos interfaces y contenedores para las clases del framework (pista: ver “Utilización del código de legado” por Martin Fowler):

enter image description here

Ahora dependemos de interfaces. Los wrappers llaman a los métodos de framework y podemos felizmente usar mocks en nuestras pruebas unitarias.

Y aquí llegamos a nuestro problema ...

Las clases del framework contienen muchos métodos que deben ser envueltos y burlado. Para lograr este objetivo, nuestro equipo de proveedores escribió una API que genera interfaces, envolturas y implementaciones de burlas con el uso de Macros C++.

Ejemplo de archivo de cabecera envoltura:

class PlanWrapper : public IPlan 
{ 
    // ... 
    WRP_DECLARE_DEFAULTS(FrameworkPlan); // macro 
    WRP_DECLARE_CSTR_ATTR(FrameworkPlanLabel); // macro 
    // ... 
}; 

El WRP_DECLARE_CSTR_ATTR Macro se define así:

#define WRP_DECLARE_CSTR_ATTR(AttrName) \ 
    virtual bool set##AttrName (LPCTSTR Value_in); \ 
    virtual bool get##AttrName (CString& Value_out); \ 
    virtual bool unset##AttrName(); \ 
    virtual bool isSet##AttrName() 

Ejemplo de archivo de envoltura CPP:

#include "StdAfx.h" 

using namespace SomeNamespace; 

WRP_IMPLEMENT_MODDICOM_DEFAULTS(FrameworkPlan) 
WRP_IMPLEMENT_W_CSTR_ATTR (FrameworkPlan,FrameworkType1, FrameworkPlanLabel) 
// ... 

se define La WRP_IMPLEMENT_W_CSTR_ATTR Macro como este:

#define WRP_IMPLEMENT_W_CSTR_ATTR(ClassName,AtrTypeObj,AttrName) \ 
    bool ClassName##Wrapper::set##AttrName (LPCTSTR Value_in) { \ 
      AtrTypeObj aValue = Value_in; \ 
     FrameworkLink<ClassName> convertedObj = NULL_LINK; \ 
     framework_cast(convertedObj, m_Object); \ 
     return convertedObj != NULL_LINK ? \ 
         convertedObj->set##AttrName (aValue) : false; \ 
    } 
    // ... 

Tenemos un montón de cosas aún más complicadas, pero creo que entiendes la idea.

El problema con la API es que es extremadamente complicado, no legible, no se puede depurar y no se puede probar.

Nos gustaría encontrar un mecanismo mejor para lograr el mismo objetivo. La idea era que usamos algunas de las características avanzadas que vienen con el nuevo compilador, como plantillas avanzadas, listas de tipos, rasgos, etc.

Con las plantillas casi podemos lograr nuestro objetivo, pero estamos atascados con los nombres de los métodos. Podemos generalizar para los tipos, pero ¿cómo manejamos los nombres de los atributos?

También pensamos en crear una herramienta para generar automáticamente el código wrapper + interfaces + mocks. Sin embargo, la API de nuestro marco externo es extremadamente complicada y escribir dicha herramienta sería muy costoso.

¿Cuál crees que es la mejor manera de resolver este problema? ¿Tal vez ya ha tratado con algo así y puede proporcionar buenos consejos? ¡Esperamos ver sus respuestas!

Respuesta

1

Creo que iría con una herramienta de generación de código. Probablemente haga algunos programas simples de utilidad: uno para generar una interfaz correspondiente a una clase de su marco heredado, uno para generar el contenedor y otro para generar el objeto simulado (o al menos un esqueleto).

Esto implica tener alguna manera de analizar el código de su marco heredado. Me gustaría echar un vistazo a Clang, o simplemente ejecutar ctags en el archivo fuente y tratar las etiquetas resultantes.

+0

Thx. Definitivamente vamos a echar un vistazo a Clang y ctags. – nowaq

0

Si el código base es lo suficientemente grande (es decir, varios cientos de miles de líneas de C++), y si se puede compilar con GCC (una versión reciente, es decir 4,6) que podría tal vez considere hacer un plugin GCC específica o una extensión MELT.(MELT es un lenguaje específico de dominio de alto nivel para extender GCC). Sin embargo, dicha personalización GCC requiere una gran cantidad de esfuerzos (semanas, no horas de trabajo).

+0

Ojalá pudiéramos ir con GCC. Desafortunadamente, estamos completamente atrapados en el compilador de Visual Studio. – nowaq

+0

+1 para usar un contenedor para código heredado o código fuente no disponible. – umlcat

1

Utilice Abstract factory en su lugar macros para resolver este problema.

class IApiFactory{ 
virtual ISomeApi1* getApi1() =0; 
virtual ISomeApi2* getApi2() =0; 
..... 
}; 

Después de implementar esta interfaz para su api normal y api moc y pasar a la instancia de la fábrica para su sistema como:

MySystem system(new NormalApiFactory); 

o

MySystem system(new MocApiFactory); 

debe ser declarada Su sistema como:

class MySystem{ 
public: 
    MySystem(IApiFactory* factory); 
}; 

En su fábrica devolverá la implementación api normal u objetos moc. Por supuesto, podría devolver fábricas "que devolverán otras fábricas u objetos" de su fábrica raíz.

+1

Pero el problema es cómo refactorizar una gran bolsa de código para este patrón ... –

+0

@BasileStarynkevitch 'El problema con la API es que es extremadamente complicado, no legible, no se puede depurar y no se puede probar.' 'Nos gustaría encontrar un mecanismo mejor para lograr el mismo objetivo. La idea era que utilizáramos algunas de las funciones avanzadas que acompañaban al nuevo compilador, como plantillas avanzadas, listas de tipos, rasgos, etc. ' Solo sugiero un mecanismo estándar para resolver este problema. – AlexTheo

+0

@AlexTheo Lo siento, tal vez la descripción puede ser un poco confusa. Al decir "API" me refería a los MACROS que usamos actualmente para generar los Wrappers. Este es el "problema" que nos gustaría reemplazar con algo más bonito/mejor/más robusto. – nowaq

0

Me gusta el generador de código. Puede crear un diccionario de todas las cosas para envolver, lleva tiempo, luego use una herramienta simple para generar el código. Además del código, también obtiene una referencia clara del sistema heredado, una guía para reemplazarlo más adelante.

0

Nuestra DMS Software Reengineering Toolkit es una herramienta de transformación de programas que lee el código fuente y lleva a cabo transformaciones arquitectónicas en él.

DMS, con su C++ Front End (VC6 y más moderno capaz de Visual Studio), es probable que se podrían utilizar para leer los archivos de cabecera para los componentes que se burlaban, y generar los simulacros.

DMS se ha utilizado para llevar a cabo transformaciones masivas en el código C++, lo que implica cambios de interfaz e interfaces de varias maneras. Vea uno de mis documentos técnicos, Re-engineering C++ Component Models Via Automatic Program Transformation.

0

Hay una manera de estructurar la generación de código de forma completamente controlada y aún así mantener toda la libertad de generar todo el material API que sea difícil.

Si utiliza Visual Studio (2005, al menos, pero preferiblemente 2008 o 2010) se puede utilizar manera mencionada de este equipo T4 de generación de código structurized - que se basa en la T4 y sólo XML:

http://blogs.msdn.com/b/t4/archive/2011/11/30/some-nice-new-getting-started-with-t4-videos.aspx

Soy el innovador y el arquitecto principal de esa metodología ADM y autor de ese blog http://abstractiondev.wordpress.com/

Lamentablemente no tengo un caso concreto para vincular para que coincida con el suyo, pero me complace trabajar la solución para usted (y en si desea beneficiar a la comunidad, publique un caso a partir de eso).