Анализ реального исходного кода OpenSource проекта. Заметки на полях
Недавно мне пришлось разбираться с достаточно известным опен-сорсным проектом ЛибрОфис. Мне показали как его скомпилировать под windows и под (разные) Линукс и, к моему восхищению, и там и там он работает почти одинаково если в качестве движка визуализации используется QT. Давайте посмотрим в каком смысле это идеальный проект, а в каком почти негативный паттерн.
Не рекомендуется для чтения лицам испытывающим нежные чуства к ОпенСоурсным проектам, такой контент может шокировать ваши чуства.
Начнем пожалуй с документации если вы попытаетесь понять как устроен проект изнутри, вас ждет загадочное извращение известных шаблонов и принципов в данном случае MVC заменили на FCM. В документации
https://wiki.documentfoundation.org/Documentation/DevGuide/Office_Development
вы можете найти такую цитату:
The well-known Model-View-Controller (MVC) paradigm separates three application areas: document data (model), presentation (view) and interaction (controller). LibreOffice has a similar abstraction, called the Frame-Controller-Model (FCM) paradigm. The FCM paradigm shares certain aspects with MVC, but it has different purposes; therefore, it is best to approach FCM independently of MVC. The model and controller in MVC and FCM are quite different things.
The FCM paradigm in LibreOffice separates three application areas: document object (model), screen interaction with the model (controller) and controller-window linkage (frame).
Если я правильно понимаю переводится это так:
MVC парадигма разделяет три области проектировния приложения: данные документа — модель, представление — вью и взаимодействие — контроллер. FCM парадигма разделяет некоторые аспекты с MVC, при этом имеет другое назначение. Понятия модель и контроллер достаточно отличаются в MVC и в FCM.
FCM парадигма разделяет три области проектировния приложения: объект документа — модель, взаимодействие экрана с моделью — контроллер и связка контроллера с окном как фрейм.
После вы можете найти в документации вот такою картинку связанности базовых объектов концепции:
Как видим из заявленной тройки объектов два почему-то потерялись, но видимо чтобы компенсировать эту потерю появились два других типа Component и Window, зачем нам расказывали до этого про модель, вью и контроллер не совсем понятно.
Справедливости ради далее нам продемонстрируют картинку с объектами трех исходных типов:
К сожалению эта картинка как и сопровождающее ее описание мало что дает для понимания практических задач которые решаются приложениями Офиса. Самая масштабная из этих задач, как вы знаете, это визуализация сложных документов и их состовляющих, а также элементов управления этими документами и способами все той же их визуализации.
К сожалению мы вряд ли можем опираться на это описание при анализе исходного кода проекта потому что все это выглядит как игра слов и жонглирование терминами без какой либо привязки к реальным практическим задачам которые решаются в исходном коде приложения. Например мне очень трудно понять, что можно иметь ввиду под взаимодействием экрана с моделью (то есть с документом) так как я привык к тому что на экране отображается некоторая проекция документа, его визуальное представление, то есть это не экран взаимодействует с документом, а документ отображается на экране. Обзывать такой тип отношения сущностей взаимодействием причем с обозначением направления этого взаимодействия определенным образом от как бы активной сущности к пассивной для меня представляется очень не адекватной попыткой натянуть какую то как бы систему парадигм на не очень удачное понимание структуры и задач проекта.
Практическая часть
А какие же практические задачи решают приложения из офисного набора? Редактор текста, редактор таблиц, редактор графических построений-изображений?
Приведенное выше описание полезно в том смысле что нам даны ключевые понятия-типы объектов которые мы должны найти в исходном коде и для которых мы должны самостоятельно понять способ их взаимодействия и способ построения их отношений в исходном коде проекта. Забегая вперед могу сказать что ключевым типом объектов на котором построена визуализация и весь привычный пользовательский визуально-графический интерфейс офисных приложений является не контроллер и не модель, не view, не фрейм! Ключевым объектом является vcl: window (по русски ВиСиеЛьное-окно) которое авторы документации не смогли поэтому не упомянуть рассказывая нам про модные MVC, FCM парадигмы, но которое является совершенно чужеродным объектом в рамках этих парадигм.
По моему здесь мы наблюдаем такой типичный пример, когда законы предметной области (в нашем случае законы рисования пользовательского интерфейса на экране) на практике победили желание реализовать способ построения проекта с позиции некоторой модной-раскрученной парадигмы. То есть у нас как бы в основе лежит MVC, но только это не совсем MVC, а некоторая модификация с названием FCM, но в основе которой используется дерево объектов с базовым типом vcl: window. И взаимодействуют эти объекты конечно не с экраном, а с вводом пользователя, который привычно осуществляется через мышь и клавиатуру, при наличии такого понятия как фокус ввода соответственно мыши и клавиатуры и при наличии активного (владеющего фокусом ввода) объекта типа vcl: window который принимает этот ввод и решает что с ним делать. Но вот это взаимодействие визуальных представлений объектов на экране и пользовательского ввода должно адекватно отображаться-отрисовываться на том же экране.
Очень много статей на Хабре посвящено супер-возможностям языка С++, много споров о том на сколько эти супер возможности действительно супер. Но что если взять и заглянуть в какой-нибудь реальный опен-сорсный проект и посмотреть на реальную практику применения языка, насколько востребованы эти супер-возможности?
Многие слышали что существуют декларативные языки, но мало кто пытался разбираться как же работает код который исполняет то что написано на таком декларативном языке. У вас есть шанс посмотреть пример такого языка и его интерпретатора. Наверно пример не самый лучший с точки зрения правильного построения такого интерпретатора, да и того что написано на декларативном языке в этом случае, но по крайней мере вы сможете избежать ошибок которые допустили разработчики в случае этого примера.
Что же мы можем посмотреть в исходном коде LibreOffice? Объектом нашего внимания будут:
1. файл декларативное описание кнопочной панели меню, так называемый тул-бар:
https://github.com/LibreOffice/core/blob/master/sw/uiconfig/swriter/ui/notebookbar.ui
и 2. C++ код который превращает (с опмощью визуальных библиотек) это описание в реальный визуальный объект на экране пользовательского монитора:
https://github.com/LibreOffice/core/blob/master/vcl/source/window/builder.cxx
Конечно класс VclBuilder из этого файла реализует это превращение с помощью библиотек визуальных объектов и разных хелперов, но основная работа по интерпретации декларативного языка реализована именно в этом классе.
Если бы вы смогли скомпилировать дебажную версию LibrOffice под Windows (а вы можете вполне скачать релизную версию под виндос) и смогли каким-то чудом запустить полученный исполняемый файл со всеми его библиотеками, в отладчике вы смогли бы увидеть примерно такой кол-стек вызовов к интересующему нас классу:
Как ни странно этот стек вызовов может нам много сказать о структуре объектов которая формируется С++ кодом из нашего декларативного описания. Код создает дерево объектов унаследованных от базового класса vcl: Window каким бы ни был текущий создаваемый объект: кнопка, выпадающий текстовый список-comboBox, список с кнопками, переключатель, он содержит (наследует) функциональность класса vcl: Window которая и позволяет добавлять его в дерево или другими словами в список дочерних элементов (листьев) родительского объекта который тоже наследуется от vcl: Window. Таким образом и функции дочерних элементов, которые позволяют присоединять дочерние объекты к родительским, и функции родительских объектов которые позволяют сохранять дочерние объекты в свой внутренний список родительского объекта реализованы в одном базовом классе vcl: Window.
Не знаю насколько понятно здесь использование термина наследники по отношению к дочерним элементам в дереве, на самом деле правильнее будет наверно говорить об отношении владения в том смысле что владеющий объект хранит (и обеспечивает порядок при перечислениях) список ссылок к своим подчиненным-внутренним объектам. Объекты подчиненные до такой степени, что им грозит удаление (уничтожение) если владеющий объект удалит их из своего списка ссылок и не передаст другому объекту на хранение.
Наверно не все понимают смысл такой организации визуальных объектов в памяти хотя смысл этот достаточно очевидный: если у вас есть некоторый визуальный объект для отображения на экране например панель с кнопками логично будет нарисовать сначала эту панель. В самом простом случае надо будет закрасить прямоугольник на экране обозначающий эту панель, потом, поверх этой закрашенной область мы сможем нарисовать наши кнопки. Согласитесь было бы гораздо сложнее нарисовать сначала кнопки, а потом закрашивать цветом фона панели свободные области в выделенном для панели прямоугольнике. Таким образом очевидно что при построении объектов ответственных за визуализацию на экране надо поддерживать эти отношения внешний (фоновый) объект — список внутренних объектов.
+ некоторые основы кодирования рисования на экране
Декларативный язык самым естественным образом поддерживает этот тип отношений между объектами как фоновый (он же родительский, он же владеющий) и внутренние (они же дочерние или подчиненные)
Как вы думаете какие знания алгоритмов понадобятся вам для анализа кода интерпретации декларативного языка? Я сам придумал этот вопрос и я теряюсь в поисках ответа на него, если вы когда то занимались анализом существующего (чужого) кода основной задачей при таком анализе является не применение алгоритмов, а выяснение той идеи которую пытался реализовать автор (при условии отсутствия нормальной документации к коду, условие которое практически всегда и везде безусловно выполняется), вам нужно получить какое-то общее представление о том, какое представление имел автор когда этот автор делал реализацию для некоторой абстрактной задачи такой как рисование визуальных элементов с элементами анимации на экране и с подключением различных функций к событиям генерируемым этими элементами. Единственным более менее эффективным методом такого анализа является формулировка гипотезы о том как этот код работает и выяснение деталей которые вообще говоря могут опровергнуть вашу гипотезу и заставить вас таким образом или скорректировать вашу исходную гипотезу или кардинально переформулировать ее, возможно разбить на части. Вы должны выяснить задачу которую ставил перед собой и, соответственно, пытался решать автор (авторы) кода. Только имея хотя бы некоторое общее представление об этой задаче вы можете оценить ту реализацию которая досталась вам в виде кода, вы должны оценить ее полноту-законченность, адекватность (решение может в принципе быть не способным решить задачу до конца и/или покрыть все возможные варианты условий или исходных данных для задачи).
Очень поверхностный анализ кода
Как же реализовано иерархическое владение визуальных объектов в коде Либр-Офиса? Давайте смотреть.
Функция VclBuilder: makeObject () создает объект нужного типа, известного фнутри этой функции, явно прописанного через свой статический метод создания экземпляра (фабричный метод) либо напрямую через вызов конструктора. И возвращает уже обезличенный объект под указателем на базовый класс vcl: Window на уровень выше в функцию VclBuilder: insertObject () .
Функция VclBuilder: insertObject () инициализирует созданный объект неизвестного типа который унаследован от базового класса vcl: Window через интерфейс этого класса стандартным образом просто передавая прочитанные из XML атрибуты-свойства текущего XML узла. Эти аттрибуты передаются этому объекту неизвестного типа, и этот объект неизвестного типа вправе использовать их для собственной инициализации так как ему удобно (он может инициализировать свои поля применив соответствующее преобразование из строки в заданный тип, а может просто проигнорировать некоторые переданные ему значения, как видите простое наследование от базового класса решает задачи инкапсуляции и полиморфизма самым простым и надежным образом.)
Функция VclBuilder: handleObject вызывает handleChild (pCurrentChild, nullptr, reader); чтобы добавить обект в дерево визуальных оббъектов …
Ну и напоследок «цитата дня» из интернета по поводу программирования пользовательских сценариев (макросов) для ЛибрОфиса:
https://forumooo.ru/index.php? topic=9795.15
14 июня 2023, 09:04
Цитата: dziglo от 13 июня 2023, 19:56Тогда не происходит замен. Ни когда выделена часть текста, ни когда выделений текста нет.
Целиком рабочий пример лучше поискать в Гугл, их много, разной степени подробности и для разных ситуаций. Выделений текста м.б. несколько, текст м.б. внутри таблицы, врезки, линкованного раздела, выделение м.б. вертикальным (блочным) итд. Главное что я хотел донести: OpenOffice|LibreOffice использует абстракцию MVC (Model-View-Controller), которая совсем не похожа на объектную модель Word/Excel в VBA, где у каждого объекта скорее всего есть свойства Text, Value, Label, Caption итд, вызываемые через точку. В LO они тоже есть, но точек будет несколько и иногда будет несколько строк, потому что не все методы/свойства доступны через точку (где-то придется вызывать функции, передавая в скобках объекты как аргумент для нее).
Если беретесь разрабатывать продукт в OpenOffice|LibreOffice — придется эту особенность принять и научится самому переходить от M к V или С и обратно, потому что в одном случае нужное вам будет в контроллере, другой раз — в модели или view.
Лучший способ кодить — кодить и искать информацию самостоятельно, упомнить ее нереально. В дни когда нет интернета — кодить в OpenOffice|LibreOffice бесполезно (частично спасают книги Питоньяка). Ситуация с объектами в LO бывает настолько сложная, что в коде приходится перебирать все элементы коллекции объектов (нельзя обратиться напрямую по имени, ID или по индексу). Да, вот так все запущено немцами. Но любой нужный результат все-таки достижим.
Если посмотреть на большие специфические проекты с макросами по обработке текста (Writer) — часто можно увидеть Python в кач-ве основного языка макросов. Он прячет некоторую часть API-дичи и кмк будет чуть легче в освоении. НО примеров и документации там еще меньше.
Возможные названия которые были в работе:
Основы программирования на декларативном языке и в С++ коде его интерпретации. Шедевр наивного программирования. Задача для рефакторинга.
Практика применения языка С++ на примере интерпретатора декларативных описаний.