Как реализовать большое количество сложных оболочек для устаревших API / framework (макросы C ++ против шаблонов C ++ против генератора кода)? - PullRequest
10 голосов
/ 14 декабря 2011

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

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

enter image description here

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

И здесь мы подходим к нашей проблеме ...

Классы фреймворка содержат много методов, которые необходимо обернутьи издевались.Чтобы достичь этой цели, наша команда поставщиков написала 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 заключается в том, что он чрезвычайно сложен, не читается, не отлаживается и не тестируется.

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

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

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

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

Ответы [ 6 ]

1 голос
/ 14 декабря 2011

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

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

1 голос
/ 14 декабря 2011

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

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

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

MySystem system( new NormalApiFactory );

или

MySystem system( new MocApiFactory );

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

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

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

0 голосов
/ 19 декабря 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/

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

0 голосов
/ 14 декабря 2011

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

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

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

0 голосов
/ 14 декабря 2011

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

0 голосов
/ 14 декабря 2011

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

...