Кактус, или как перестать грызть Lazarus

| рубрика: Заметки | автор: st
Метки: , ,

"Мыши плакали, кололись, но продолжали грызть кактус..."

Из новорусского фольклора

Присказка

Первый раз о тестировании сладкой парочки, состоящей из Free Pascal (FPC) - компилятора и библиотек общего назначения FCL, и Lazarus - библиотеки компонентов LCL и среды разработки, я написал еще в 2010 году. Выводы были не слишком утешительные.

С той поры минуло 6 лет, за которые довелось не только разработать несколько небольших проектов на FPC/Lazarus (например, Firebird profiler), но и проделать реальный эксперимент по миграции достаточно большой (около 1М строк) системы класса Small ERP. Предупрежу сразу, что эксперимент я волюнтаристски прервал ввиду превращения Lazarus в тот самый кактус из фольклора.

Сказанное не значит, что Кактус плох. Инструмент вполне пригоден для профессиональной коммерческой разработки. Для себя я установил эмпирический лимит в 100К строк, за которыми начинаются проблемы, систематическое и героическое преодоление которых будет стоить намного дороже, чем лицензии на Delphi Professional. Для других классов приложений лимит может оказаться иным, но не думаю, что порядки величин будут различаться.

Вначале вспомним о Delphi, которая с версии 2009 за 7 лет прошла путь революционных изменений и улучшений. Назову лишь некоторые:

  • поддержка Unicode (прозрачная для программиста);
  • кросс-платформенная разработка (Windows 32/64, Mac), в том числе для мобильных устройств (iOS, Andriod), компилятор для Linux анонсирован в следующей версии 10.2;
  • линейка компонентов FireDAC. Универсальный доступ к БД с нулевым развертыванием;
  • развитие языка: generics, анонимные функции, метаданные...;
  • поддержка составных имен для модулей типа System.Generics;
  • look and feel стили для оконных приложений;
  • и многое другое.

Главное преимущество Кактуса - кросс-платформенность, перестала таковой быть. С учетом моих не самых приятных воспоминаний о кросс-разработке под WinCE - перестала быть вполне заслуженно. Но! Не будем забывать, что FPC (не Кактус) поддерживает большее число платформ, и в ряде случаев лучше оказаться с "голым" компилятором командной строки и библиотеками общего пользования, чем переходить на более низкий уровень голого Си и Макроассемблеров. Для той же Raspberry Pi, например. Но, еще раз подчеркну, речь идет только о FPC.

Основное поле Кактуса - разработка под Windows, включая кросс-платформенную. Если вы посмотрите на статистику загрузок на sourceforge, то Кактус для Windows лидирует с огромным отрывом. И на этом поле он входит в прямую конкуренцию с Delphi.

Сказка

Пора от присказки переходить к реальным историям, напоминающим порою сказку.

Сама идея эксперимента была инспирирована имеющимся в сети "кейсом" миграции ERP-системы где-то в Восточной Европе, датированной примерно 2004 годом. Ссылку я искать не буду, погуглив, можно найти текст на английском языке. По здравому смыслу, спустя 10 лет ситуация должна быть намного лучше.

Очевидно, что систему в 1М строк очень трудно мигрировать единовременно. Почти нельзя. И систему в 100К строк тоже. Программу из 10К строк, в принципе, можно. В окрестностях 2004 года, когда стартанул .NET, на рынке было множество краткосрочной работы по переносу таких небольших Delphi-поделок на VB.Net-поделки. Почему использован уничижительный термин "поделки"? Потому что если программа в 10К строк написана качественно, то она делает свое дело, новых блокирующих ошибок не возникает, затрат на сопровождение немного. Соответственно, в здравом уме и рассудке нет причин переписывать код в надежде, что новая версия окажется еще лучше.

Почему "почти нельзя" единовременно мигрировать систему большего масштаба? Потому что система живая, в нее постоянно вносятся изменения. Если выбрать путь изолированной миграции с подкачкой изменений из основной ветки, то штат разработчиков придется утроить. Треть будет заниматься поддержкой и развитием текущей версии, треть - миграцией, треть - синхронизацией двух веток. Грубо, но примерно так. Штат для тестирования тоже придется удвоить, как минимум.

У этой схемы есть один крупный недостаток. Миграция происходит где-то сбоку от основного производственного процесса, и в один прекрасный день возникает совершенно новая система, которой надо, начиная с момента Х, заместить систему старую. Это очень серьезные риски и стрессы.

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

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

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

По условию, код должен компилироваться совместно как в Кактусе 1.4, так и в Delphi 7. Вооружившись \$IFDEF эта часть работы была выполнена за относительно небольшое время, включая тесты, о которых ниже. Некоторые сложности возникли с именованиями модулей. Речь не о дурной привычке Кактуса переименовывать файлы в нижний регистр, это как раз настраивается. Правда, настроить нужно на всех рабочих местах и объяснить программистам...

Коллизии были в другом. Например, в системе уже есть свой EventLog.pas. Но он есть и в библиотеках FPC. Поэтому компиляция рожала странного вида ошибки: предупреждение о неверной контрольной сумме EventLog.pas, после чего выдавалась ошибка, что EventLog.pas и вовсе не найден. Ожидалось нечто более информативное. Ладно, разобрались. Модуль пришлось переименовать. Соответственно, все ссылающиеся на него uses тоже.

Одновременно проверке на совместимость были подвергнуты библиотеки, использующиеся на уровнях ядра и служб. Здесь ситуация была сносной. UniDAC компилировался под Кактусом 1.4 без проблем, Indy 9 потребовала добавления кучи \$IFDEF, но тоже компилировалась, малютки zlib и regex входят в состав FCL, PascalScript тоже, хотя далеко не новой версии. В целом, после некоторых усилий все компилировалось без ошибок.

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

Объем собственного (без тестов) кода уровня ядра и сервисов составлял как раз порядка 100К строк. Результат неплохой, но это лишь вершина айсберга. Первая часть марлезонского балета эксперимента на этом завершилась.

Дальше начинался лес графических приложений и соответствующих компонентов.

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

В рамках переделки и создания новых приложений Кактус выглядел вполне рабочим инструментом. Палитра компонентов, правда, была в 2015 году заметно жиже, чем у Delphi из 2001 года, но это не главное. Главное - проблема совместимости с используемыми сторонними компонентами, которая проявилась во всей красе. По сути, кроме SynEdit ничего из используемого в Delphi 7 не могло быть повторно применено в Кактусе. Прежде всего система отчетов на QuickReport, компоненты делового планировщика, компоненты SM. Всего в разработке использовалось порядка 20 разных сторонних линеек компонентов и библиотек (все с исходниками, разумеется), на их основе строились уже свои наборы для реализации форм и логики.

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

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

Первое серверное приложение немедленно вызвало затык. В Кактусе не оказалось библиотек для разработки веб-служб. Имеется сторонний проект WST (web services toolkit), поддерживаемый одним программистом. Сделан он добротно, поэтому в случае форс-мажора в исходниках можно было разобраться. После отсылки автору бага по несовместимости с Delphi 7, проблема была быстро исправлена и, таким образом, дельфовое приложение-клиент могло употреблять этот сервис внутрь с аппетитом.

С модульными тестами ситуация выглядела плохо. Имеющаяся в Кактусе подсистема LazUnit ни разу не совместима с DUnit. Сигнатуры вызовов проверок совершенно разные. Пришлось дописать сверху слой адаптеров, позволяющих совместно использовать существующие тесты уровня ядра и служб и добавлять новые. Сама графическая оболочка для работы с тестами выглядела очень убого, эргономика и выдача информации были крайне неудобными по сравнению с DUnit. Например, в DUnit запуск оболочки по F9, а после запуска по той же F9 - запуск тестов. Два нажатия не снимая пальца с курка. В LazUnit - сначала F9, потом Ctrl+R. С учетом сотен запусков в день при отладке... Оболочку пришлось дорабатывать. После чего изменения были предложены к внесению в основную кактусовую ветку разработки. Не беда, что изменения затерли другими, с третьей попытки получилось. Надеюсь, и сейчас они работают. Желающие могут посмотреть протокол на баг-треккере (раз - влили, два - регрессия).

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

В 2015 году Кактус по-прежнему не поддерживал работу с группой проектов. То есть, например, для отладки основного приложения и модульных тестов нужно одновременно запускать два Кактуса. А для отладки DLL тоже два, переключаясь между ними в зависимости от того, где надо перехватить управление.

Отладочная информация? Да таковая в каком-то виде присутствует. Переменные простых типов можно видеть при отладке. Свойства классов - уже нельзя, если за свойством не переменная и не включен специальный режим генерации. Например, посмотреть содержимое списка MyStrings[7] напрямую нельзя. Delphi еще в первых версиях умела не только показывать подобные величины, но и свойства с вызовом нижележащих функций GetXXX. У Кактуса почему-то за 17 лет так и не сложилось с интерактивной отладкой. Видимо, основные пользователи довольствуются журналами трасс и сообщениями.

Эта неприятная особенность в полной мере проявилась, например, при отладке компилятора скриптов, когда приходилось журналировать трассы практически в масштабе "один-в-один". Среда разработки становилась по сути продвинутым текстовым редактором. При отладке дженериков отладчику постоянно сносило крышу, точки останова произвольно смещались на одну-две строки ниже реальной. То есть останов показывался на строке N, а реально выполнение было еще на N-1!

Поезд все не набирал хода, но я фиксировал, что времени на Кактус уходит все больше и больше.

Никто из команды FPC не собирался исправлять баги в 2.6.4, стандартный ответ: "Поставь себе 3.0!" "А есть Кактус с поддержкой 3.0?" - "Релиза нет, но собери текущую версию, должно заработать..."

"Должно заработать..." Бл@, простите мой французский, ребята, у вас как вообще язык поворачивается предлагать использовать для производства какую-то "текущую версию" среды разработки? Может, заодно и линукс из dev-веток ставить и компилировать? По ссылке есть примерно десяток моих баг-репортов, не обращайте внимание, что они помечены resolved. Они ни хрена не resolved, они на самом деле cancelled (sic!), потому что FPC 2.6.4 давно не поддерживается. И приходилось окольными путями обходить кривизну generics, когда класс не мог рекурсивно ссылаться сам на себя. Приходилось бороться с юникодом внутри Кактуса и его отсутствием в FPC. Приходилось удивляться багам в RTL.

Но вот настал долгожданный день. Вышел Кактус 1.6 с поддержкой FPC 3.0, где юникод, вроде как нормальные дженерики и другие исправления. Попробуем? Попробуем!

После установки перестал собираться UniDAC. Разбор полетов выявил кусок кода.

program Project1;

{$MODE DELPHI}

uses
  SysUtils, Classes, DB;

procedure InternalCreateFieldDefs(Fields: TFields; FieldDefs: TFieldDefs);
var
  F: TField;
  FieldDef: TFieldDef;
begin
  begin
    with F do
    begin
      FieldDef := FieldDefs.AddFieldDef;
      // in FPC 3.0.0 Error: No member is provided to access property
      // in FPC 2.6.4 compiles OK
    end;
  end;
end;

begin
end.

В чем причина ошибки? Ребята, поддерживающие FCL, решили добавить в класс библиотеки общего пользования TField свойство FieldDef. Ну, вот так захотелось им. А чо такова? С парнями вышла небольшая дискуссия на общие темы, оказавшейся последней каплей в чаше накопившихся проблем.

Да, спустя еще несколько месяцев UniDAC адаптировал исходники, чтобы они могли компилироваться под Кактусом 1.6. Но при этом исходники перестали компилироваться под Кактусом 1.4! Стандартный ответ доброхотов от опенсурса: "А зачем тебе прежняя версия, возьми новую, она же бесплатная!"

К счастью, в то время нам было уже все равно. Прерванный эксперимент с Кактусом означал старт миграции на Delphi XE 10. Все наработанные на Кактусе новые приложения были перенесены в "десятку" за две недели. Точнее, за две человеко-недели. Что показывает отличный уровень совместимости, но уже Delphi. Только в коде ядра и служб появилось несколько $IFDEF D23P.

Тут и сказочке конец?

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

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

Небольшая отсылка к истории. FPC возник более 25 лет назад, как альтернатива Turbo/Borland Pascal. Поэтому и в 2016 году создав программу "Здравствуй, мир" без каких-либо директив компилятору вы получите на выходе модуль, совместимый (например по регистрам и распределению памяти) с генерируемым Turbo Pascal. Кактус появился позднее, в 1999 году уже как альтернатива Delphi, но на базе все того же компилятора и библиотек общего пользования FCL.

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

Упомянув лимит в 100К строк я имел в виду прежде всего графические бизнес-приложения, требующие использования сторонних компонентов. Если речь идет об автономных серверных модулях, для которых не нужно ничего, кроме того, что уже есть в относительно стабильной FCL (а не в кактусной LCL), то вы ограничены только своей фантазией и скоростью набивания кода. Тем более, что немалую часть библиотек придется обустраивать на свой лад.