Что такое ROOT? Критика и апология

/ / hep-sw :: , ,

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

Важно в первую очередь определить, что же мы будем называть здесь фреймворком, и почему ROOT — это вот оно. Я настаиваю на номенклатурном использовании устоявшейся кальки (пусть и неблагозвучной) — прямой транслитерации с английского framework, как ставшего наиболее общеупотребимой нормой в среде промышленных программистов. Хотя в ранней отечественной литературе подобную схему организации программного обеспечения когда-то принято было называть каркасом, впоследствии эти термины существенно разошлись, и это необходимо иметь в виду.

Каркас, или framework в том виде в котором его понимали в 80-90-х, это в известном смысле антонимичное понятие библиотеке. Наиболее доходчиво будет понимать каркас как среду в которую "вставляются" заменяемые библиотеки (это соответствует жёсткому размещению непереносимых в памяти программ — ныне забытой особенности организации ранних ЭВМ). Если библиотека определяет, как должна выполняться та или иная операция, каркас должен был определять в каком месте это типично происходит, являясь таким образом набором типичных сценариев использования более элементарных операций, реализованных, понятно, в библиотеках.

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

Потому что современные фреймворки устроены иначе: наряду с набором типичных сценариев с точками расширения, они реализуют и собственный набор более элементарных функций, которые в оригинальном "каркасе" должны были бы оказаться делегированными библиотекам. Так что само различие, дихотомия между фреймворком и библиотекой стёрлись, и первый просто обычно превосходит вторую, предоставляя, как правило, более широкий набор общих (более высокого уровня) процедур, нежели классическая библиотека, однако по-прежнему в виде API.

Примером таковой гибридизации, по сути классическим фреймворком в современном понимании и является ROOT, "framework for data analysis": это набор библиотек объединённых более-менее связным и совместимым API с пластичными сценариями взаимной интеграции.

Так например, функции нескольких библиотек, включённых в него:

  • libThread — библиотека реализующая функциональность POSIX-потоков в виде класса C++ (POSIX treads изначально реализованы под чистый Си)
  • libFFTW — обёртка над внешней библиотекой (или самостоятельная реализация) быстрого преобразования Фурье
  • libHistPainter — собственная реализация отрисовки гистограмм
  • libTree — библиотека для работы с собственным форматом хранения данных, типично применяемым для хранения физических событий

В то же время библиотека libGui довольно жёстко фиксирует использование собственных библиотек ROOT для реализации графического пользовательского интерфейса, а libDict вовсе позволяет сериализовать экземпляр произвольного класса C++, что соответствует довольно высокоуровневым сценариям. Ну и конечно libCling, как и само окружение CLING (ранее CINT) реализует исполнение интерпретируемых сценариев на собственном диалекте C++.

При этом для ROOT характерны довольно архаичные черты, пришедшие из тех времён, когда в ООП видели панацею от всех затруднений при проектировании большого ПО: жёсткое централизованное API (в идеале всякий класс отнаследован от общего для всех предка — TObject), отсутствие встроенных механизмов RPC, слабая гранулярность кода в целом.

Эти черты — результат многолетней истории ROOT, о которой лучше всего почитать в интервью с создателем системы, Rene Brun'ом. Из истории же делается понятно и пренебрежение многими прогрессивными концепциями и практиками разработки ПО на C++: обобщённым программированием (шаблонами), практически полное игнорирование встроенных механизмов RTTI и тотальная склонность к изобретению велосипедов (чего стоит только рукодельный XML-парсер, игнорирующий 80% стандарта XML, зато чувствительный к пробелам и переносам строк).

Я попробую теперь изложить один взгляд на то что же сделало ROOT таким популярным решением в High Energy Physics.

Достоинства

  • Бесподобное удобство и богатство функций классов гистограмм. Эта часть ROOT — хотя и не слишком качественно написанный, тем не менее, проработанный со всей возможной детальностью, в основном физиками для физиков, множество раз оттестированный компонент фреймворка. Здесь создатели ROOT удачно употребили опыт полученный в процессе разработки PAW. Хотя сейчас декорации этих гистограмм и выглядят архаично, из-за их чрезвычайного удобства, всякое новьё очень не скоро сможет потеснить их на пьедестале всеобщего признания.
  • Продуманный и эффективный механизм хранения данных: ROOT TTree — de facto стандарт представления физической информации. Являясь по существу сериализованными и сжатыми array of structures, дополненными интроспективной информацией, а по принципу — логическим развитием NTuples, TTree почти предельно-эффективный компромисс между скоростью I/O и размером.
  • C++ как язык интерпретируемых сценариев. Пользователь-физик, как легко догадаться, слабо расположен к изучению domain specific languages и выбору выразительного средства наиболее подходящего конкретной задаче. Гораздо проще ему, худо-бедно научившись одному какому-то искусственному языку, выражать на нём всё и во всех местах, при желании легко получая из медленного в работе, но быстрого в написании интерпретируемого сценария, быстрый в исполнении, но (как правило) сложный в написании и отладке код. "Скомпилировать скрипт в библиотеку".
  • К условному достоинству можно отнести и прочее, разное по качеству и степени завершённости творчество контрибьюторов, вроде обёрток над сокетами для сетевого взаимодействия, реализации pipeline'ов для persistant data с дискриминацией, и много чего ещё, на не слишком взыскательный вкус.
  • Сообщество. Физики для физиков склонны мотивировать откровенно-неудачные в долгосрочной перспективе технические решения понятными соображениями немедленного удобства. В условиях современной науки, такое живое, дышащее сообщество, ориентированное на "результат вчера" — скорее плюс.
  • Документация. Во-первых, в ROOT она хотя бы есть (если вы понимаете о чём я — применительно к открытому научному ПО это редкость). Во-вторых, сообществом не только декларируется, что она должна быть более полной, но и ведётся какая-то работа. Это не спасёт вас от часов реверс-инжиниринга в коде, но по крайней мере снабдит работающими сниппетами кода.
  • Открытость и доступность. Спасибо, CERN, что ты есть.

Недостатки

  • Это higly cohesive, rigid software. ROOT всюду всюду пытается выступать средой в которую вы что-то интегрируете, он диктует принципы организации кода, он инвазивен и обтрузивен — вплоть до того что стремится всегда перехватить даже системные обработчики сигналов в работающем приложении. Когда вы любите писать в заголовочных файлах static const, это едва ли будет для вас проблемой. Но стоит вам попытаться выделить обработчик событий GUI в отдельный поток, чтобы управлять машиной состояний в реальном времени из каких-то дополнительных источников, начинается настоящая головная боль...
  • ROOT — это классический bloatware с исполинским technical debt.
  • Это сложно передать без конкретных примеров, но местами (и подчас довольно большими, чёрт побери, местами!) код ROOT невозможно плох. System call для определения версии компилятора, различные задержки по таймеру, чтобы обойти плохо организованные параллельные операции, куча избыточной статической реализации, бесконечный "закат солнца вручную" с разбором текстовых сообщений вызовами strcmp(), несконачемый le bricolage — от уже упомянутого XML-парсера до авторского видения ущербных регулярных выражений. Первый похоронил замечательную идею GDML в зачатке, а вторые настолько плохи, что почти нигде не используются в самом ROOT! Недоделки и (un)known issues станут вашими спутниками на весь цикл сопровождения эксперимента. Лучшим тому подтверждением является многочисленность попыток "довести до ума" этот фреймворк командами с различной компетенцией — FairROOT, PandaROOT, AliROOT, счёт идёт на десятки.
  • Архаичность. Времена больших фреймворков, в которых люди самостоятельно реализовывали интроспекцию типов, рефлексивность кода и унифицировали всякую сущность через наследование от общего предка канули в Лету. И не потому что мода сменилась, а потому что в исторической перспективе подход оказался нежизнеспособным в виду человеческого фактора. А ведь это краеугольный камень фреймворка в том (современном) смысле о котором мы сегодня говорим. Метапрограммирование, софтверные экосистемы, обмен сообщениями, RESTful API, распределённые хранилища, масштабирование, метаданные и дупликация — ни тени этих современных концепций вы не найдёте в ROOT.
  • Ну и классический недостаток систем подобного рода, закономерный кризис любого проекта эпохи больших API — с какого-то момента все они становятся "фреймворком всего", стремясь с различной степенью успешности воспроизвести функциональность внешних библиотек и инструментов.

Итог

ROOT — это популярный инструмент, с исторически-обусловленными недостатками, по существу герметизирующими его в нише High Energy Physics. Построенный на изначально прогрессивных для своего времени концепциях, к настоящему моменту он достаточно устарел, чтобы уступить место менее ригидным решениям, из которых, впрочем, пока ни одно не оказалось достаточно состоятельным, чтобы потеснить его на этом нишевом Олимпе.

Каким бы ни был инструмент в общем, то объективное соображение что, с учётом его популярности, попросту нет ничего лучше (то есть, один исследователь-то может, наверное, писать на Python, хранить в Apache Thrift, масштабировать в Kubernettes и визуализировать в D3.js) делает его качество вторичным, оно диктует простые рамки профессиональной этики: мы работаем в команде, мы пишем для команды, нам важен X-check и совместимость.


Comments