Как стать автором
Обновить

Архитектура кода программного обеспечения: декорируем стратегией. Рассказ в 10 эпизодах, основанный на реальных событиях

Время на прочтение20 мин
Количество просмотров11K
Всего голосов 15: ↑10 и ↓5+5
Комментарии9

Комментарии 9

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

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

Но модульность у меня двоякая. Проектирование и тестирование должно использовать бинарную модульность, практически, плагины. А конечный проект, формально монолитный, но структурно модульный, должен генерироваться автоматически из независимого кода модулей (плагинов) в общий код проекта, с помощью скрипта на Питоне.

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

Но это все «теория». Начал я свои эксперименты (недавно) с самого простого варианта. Сгенерировал мастером VS C++ простейшее оконное приложение на WinAPI, содержащее всего два пункта меню: «Выход» и «О программе», которое оформил не в виде диалога, а виде дочернего окна. Последний удалил и преобразовал в виде плагина. Однако, несмотря на кажущуюся простоту, связь между главной программой и ее плагином «About», оказалась достаточно сильной. Это и общие переменные (нужно выработать стратегию по работе с ними) и конструирование объединенного меню и, главное, совместная, непротиворечивая, работа всех циклов сообщений. Здесь, как ни странно, трудности возникли с закрытием дочернего окна, в некоторых случаях происходят утечки памяти и зависание программы с ее полным крэшем.

Хотелось бы найти готовый прототип подобного примера в Интернете, но плагины там не любят работать с окнами, только с консолью. Поэтому приходится изобретать очередной велосипед самому.
Довольно интересная история из вашего опыта.

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

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

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

WinAPI либо WTL / ATL на С++, это, в принципе, все, что мне надо. Но чтобы реализовывать там паттерны (идеи, в моем понимании), нужно понимать протоколы работы функций и компонент WinAPI, т.е., знать документацию, типа MSDN.

Вот я и споткнулся там, на «правильном» протоколе удаления дочернего окна. Документация говорит, что, в дочернем окне надо использовать вызовы: «DestroyWindow(hWnd); break;», при сообщении WM_CLOSE, и вызовы: «PostQuitMessage(0); break;», при сообщении WM_DESTROY. Но это-то как раз и приводит к краху при выходе из приложения, при открытом дочернем окне. Но если его вручную закрыть, то программа, потом, завершается нормально.

Естественно, использовал уже различные мыслимые и немыслимые варианты, как со стороны главного окна, так и со стороны «плагина». Тем более что я могу отслеживать все сообщения, и дочернего окна, и его родителя. Но все упирается в отсутствие механизма «хорошего» удаления окна (созданного в dll), во всех случаях. Иногда срабатывает и обычный способ, но не всегда.

Сейчас пытаюсь анализировать опенсорс с поддержкой плагинов. Естественно, есть какой-то нюанс, который я пока не понимаю. Но сама идея бинарной модульности для целей разработки и тестирования приложений с последующей сборкой «монолитного» проекта скриптом на Питоне, достаточно интересна. Однако, Дьявол, как всегда кроется в деталях… :)
Emelian, мы с вами в этом треде комментария обсуждаем слегка другую тему, чем та, которая поднимается в статье. Как я понял, вы говорите о том, как чудесно было бы быстро собирать приложения, имея в вооружении набор неких плагинов или бинарных модулей. То есть речь идёт про профит при непосредственных начальных проектировании и конструировании приложений.

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

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

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

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

Как я уже заметил, даже кажущиеся относительно независимые части приложения имеют, на самом деле, сильную связь. Разорвать которую, иначе говоря, распараллелить используемую программную модель, достаточно сложно. Но, пока, интересно. Тем более что в век потоков, субъядер и многопроцессорным систем этим сам Бог велел заниматься. Кстати, как вы управляете потоками в своем приложении?

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

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

P.S. Что касается моей проблемы с закрытием дочернего окна, созданного в плагине, то, скорее всего, это связано с тем, что при завершении работы приложения, не осуществляется выгрузка dll, хотя я даю команду на это. Операционная система, она ведь «умнее» программиста, она просто уменьшает счетчик ссылок на объекты этой длл и выгрузит ее не раньше, чем он обнулится. А у меня ведь плагин еще и дополнительный пункт меню из dll создает. В общем, нужен как бы мониторинг всех объектов, работающих с ресурсами длл. Да, и в теории плагинов написано, что выгрузка длл, это не тривиальная задача. Нужно ли с этими вещами разбираться? Очевидно, каждый ответит по-разному.

Наконец дочитал до конца. Именованные эпизоды помогли разделить чтение на несколько вечеров, и, возвращаясь в очередной день, быстрее погружаться в контекст вновь. Отдельный плюс автору за такой «сериал».

По теме. Очень даже годно получилось. Захотелось сразу попробовать подход в боевом проекте. Несколько таких оберток почти готовы к проду. Есть минусы:

  1. На выходе гораздо больше кода.

  2. Дольше продумываешь детали, например, какую часть логики превратить в стратегию.

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

Плюсы:

  1. Компоненты реально маленькие и простые.

  2. Декораторы можно менять местами.

  3. Стратегии можно выкидывать, добавлять новые; и это реально ничего почти не стоит.

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

Что из этого получится, может напишу еще, когда получу реальную пользу от поддержки.

Спасибо за практические знания. Если есть подобные подходы, делитесь еще)

Благодарю за комментарий! Надеюсь, все минусы окупаются плюсами.

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

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


Я лично очень скептически отношусь к IDE-шным дебаггерам. Считаю, что они сильно расслабляют разработчика. Поэтому сам предпочитаю использовать другие механизмы отладки.

Что можно сделать?

Во-первых, данная архитектура предполагает наличие очень много очень тривиальных компонентов. А допустить в простом компоненте ошибку сложно. Допустим всё решил человеческий фактор - ошибку всё-таки допустили.

Во-вторых, в статье я указывал, что всё требует покрытия юнит-тестами, и компоненты данной архитектуры - не исключение. Допустим, юнит-тесты не отловили ошибку.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий