Как реализовать большое количество сложных оболочек для устаревшего API/фреймворка (макросы С++, шаблоны С++ и генератор кода)?

Мы работаем с очень старой унаследованной системой, реализованной на C++ с помощью компилятора VC6. Сейчас мы находимся в процессе рефакторинга кода. Мы также перешли на компилятор VC9.

Мы используем внешнюю проприетарную структуру, которая также представляет собой устаревший код и не подлежит модульному тестированию. Чтобы сделать наш код пригодным для модульного тестирования, мы ввели интерфейсы и оболочки для классов фреймворка (подсказка: см. «Работа с устаревшим кодом» Мартина Фаулера):

введите здесь описание изображения

Теперь мы зависим от интерфейсов. Обертки вызывают методы фреймворка, и мы можем с радостью использовать макеты в наших модульных тестах.

И вот мы подошли к нашей проблеме...

Классы фреймворка содержат множество методов, которые необходимо обернуть и смоделировать. Для достижения этой цели наша команда поставщиков написала API, который генерирует интерфейсы, оболочки и имитирует реализации с использованием макросов C++.

Пример заголовочного файла оболочки:

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

Макрос WRP_DECLARE_CSTR_ATTR определяется следующим образом:

#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 ()

Пример cpp-файла-оболочки:

#include "StdAfx.h"

using namespace SomeNamespace;

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

Макрос WRP_IMPLEMENT_W_CSTR_ATTR определяется следующим образом:

#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; \
    }
    // ...

У нас есть куча еще более сложных вещей, но я думаю, вы поняли идею.

Проблема с API в том, что он чрезвычайно сложный, нечитаемый, не отлаживаемый и не тестируемый.

Мы хотели бы придумать лучший механизм для достижения той же цели. Идея заключалась в том, что мы используем некоторые расширенные функции, которые появились в новом компиляторе, такие как расширенные шаблоны, списки типов, трейты и т. д.

С помощью шаблонов мы почти можем достичь нашей цели, но мы застряли с именами методов. Мы можем обобщать типы, но что делать с именами атрибутов?

Мы также думали о создании инструмента для автоматической генерации кода обертки + интерфейсы + моки. Однако API нашего внешнего фреймворка чрезвычайно сложен, и написание такого инструмента было бы очень затратным.

Как вы думаете, как лучше всего решить такую ​​проблему? Может быть, вы уже имели дело с чем-то подобным и можете дать дельный совет? Мы с нетерпением ждем ваших ответов!


person nowaq    schedule 14.12.2011    source источник


Ответы (6)


Я думаю, что я бы пошел с инструментом генерации кода. Я бы, вероятно, сделал несколько простых служебных программ: одну для создания интерфейса, соответствующего классу вашего устаревшего фреймворка, одну для создания оболочки и одну для создания фиктивного объекта (или, по крайней мере, скелета).

Это подразумевает наличие какого-то способа анализа кода вашего устаревшего фреймворка. Я бы посмотрел Clang или, возможно, просто запустил ctags в исходном файле и обрабатывать полученные теги.

person Luc Touraille    schedule 14.12.2011
comment
Спасибо. Мы обязательно рассмотрим Clang и ctags. - person nowaq; 14.12.2011

Используйте Абстрактную фабрику вместо макросов, чтобы решить эту проблему.

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

После реализации этого интерфейса для вашего Normal API и moc API и передайте экземпляр вашей фабрики в вашу систему, например:

MySystem system( new NormalApiFactory );

or

MySystem system( new MocApiFactory );

Ваша система должна быть объявлена ​​как:

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

На вашей фабрике вы вернете обычную реализацию API или объекты moc. Конечно, вы можете вернуть фабрики, «которые вернут другие фабрики или объекты» из вашей корневой фабрики.

person AlexTheo    schedule 14.12.2011
comment
@BasileStarynkevitch «Проблема с API в том, что он чрезвычайно сложен, нечитаем, не поддается отладке и тестированию». «Мы хотели бы придумать лучший механизм для достижения той же цели. Идея заключалась в том, чтобы использовать некоторые расширенные функции нового компилятора, такие как расширенные шаблоны, списки типов, трейты и т. д.». Я просто предлагаю стандартный механизм решения этой проблемы. - person AlexTheo; 14.12.2011
comment
@AlexTheo Извините, возможно, описание может быть немного запутанным. Говоря об API, я имел в виду МАКРОСЫ, которые мы в настоящее время используем для создания Wrappers. Это проблема, которую мы хотели бы заменить чем-то более приятным/лучше/надежнее. - person nowaq; 14.12.2011

Если кодовая база достаточно велика (т.е. несколько сотен тысяч строк C++), и если вы можете скомпилировать ее с помощью GCC (последняя версия, т. е. 4.6), вы могли бы, возможно, подумать о создании специального подключаемого модуля GCC или расширение MELT. (MELT — это доменный язык высокого уровня для расширения GCC). Однако такая настройка GCC требует значительных усилий (недели, а не часы работы).

person Basile Starynkevitch    schedule 14.12.2011
comment
Я хотел бы, чтобы мы могли пойти с GCC. К сожалению, мы полностью застряли на компиляторе Visual Studio. - person nowaq; 14.12.2011
comment
+1 за использование оболочки для устаревшего кода или недоступного исходного кода. - person umlcat; 15.12.2011

Мне нравится генератор кода. Вы можете создать словарь всех вещей, которые нужно обернуть, это требует времени, а затем использовать простой инструмент для генерации кода. Помимо кода, вы также получаете четкую ссылку на устаревшую систему, руководство по ее замене позже.

person pinxue    schedule 14.12.2011

Наш набор инструментов для реинжиниринга программного обеспечения DMS – это инструмент для преобразования программ, который считывает исходный код и выполняет произвольные преобразования на нем.

DMS с его интерфейсным интерфейсом C++ (с поддержкой VC6 и более современной Visual Studio), его, вероятно, можно было бы использовать для чтения файлов заголовков для имитируемых компонентов и создания макетов.

DMS использовался для выполнения масштабных преобразований кода C++, особенно связанных с изменением интерфейса и перетасовкой интерфейсов различными способами. См. один из моих технических документов, Реинжиниринг моделей компонентов C++ с помощью автоматического преобразования программы. .

person Ira Baxter    schedule 14.12.2011

Существует способ структурировать генерацию кода полностью контролируемым образом и при этом сохранить полную свободу генерировать любые сложные вещи API, которые вам нужны.

Если вы используете Visual Studio (по крайней мере, 2005, но предпочтительно 2008 или 2010), вы можете использовать упомянутый способ создания структурированного кода этой команды T4, который основан только на T4 и XML:

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

Я новатор и ведущий архитектор методологии ADM и автор этого блога http://abstractiondev.wordpress.com/

К сожалению, у меня нет конкретного случая, чтобы связать его с вашим, но я рад предложить вам решение (и, если вы хотите принести пользу сообществу, опубликуйте пример из этого).

person Kallex    schedule 18.12.2011