среда, 22 января 2020 г.

[prog.c++] Шаблоны, но уже не против копипасты.

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

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

Софтина старая, потроха запутанные, оригинальные ее авторы уже недоступны. Написана в виде монолита и нет разделения на отдельные подсистемы/модули со своими интерфейсами, которые можно было бы протестировать в отдельных тестовых окружениях. Переделать структуру вряд ли реально, т.к. проще все переписать заново. Поэтому нужно как-то выкручиваться.

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

С имитацией методов для обновления информации все оказалось просто: был описан интерфейс (абстрактный класс с чистыми виртуальными методами), который прятал за собой реальную работу. Очередь при своем создании получала ссылку на реализацию этого интерфейса. Соответственно, в тестовом стенде использовалась одна реализация, а в реальном приложении -- другая.

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

Поэтому класс очереди был реализован в виде шаблонного класса. Параметризовался этот класс типом-трейтом. И в этом трейте уже описывались все необходимые псевдонимы типов и методы для извлечения нужной очереди информации из описания клиента. Т.е. тип-трейт был чем-то вроде:

struct testing_traits_t {
   using client_type = testing_client_info_t;

   static first_property_type get_first_property(const client_type * cln) {...}
   static second_property_type get_second_property(const client_type * cln) {...}
   ...
};

Соответственно, для тестового стенда класс очереди параметризовался тестовым трейтом, а для использовании внутри приложения -- актуальным.

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

Вот такое вот немного нестандартное применение шаблонов. Не столько для написания обобщенного кода, сколько для абстрагирования от реального окружения, в котором затем шаблону нужно было работать.

Примеров реального кода не привожу, да и вещи, в общем-то своими именами не называю по вполне очевидным причинам.

PS. Желающим поговорить про то, почему все это неправильно и что правильнее было бы разделить приложение на отдельные модули со строго определенными интерфейсами, что позволило бы использовать mocking при тестировании и пр. правильные вещи, могу сказать одно: добро пожаловать в реальный мир.

PPS. Если вы сами владеете старым C++ным или C-шным проектом и испытываете сложности с его поддержкой/развитием, то вам сюда. Возможно, мы сможем помочь ;)

Комментариев нет: