К содержимому
14 авг. 2026 г.·7 мин чтения

Бизнес-логика Delphi застряла в ваших формах?

Найдите и отделите бизнес-логику Delphi, скрытую в формах VCL, элементах данных, событиях dataset и коде транзакций.

Бизнес-логика Delphi застряла в ваших формах?

Формы Delphi часто работают как исполняемые спецификации, поверх которых надет пользовательский интерфейс. Нажатие кнопки рассчитывает скидки, TDBEdit.OnExit нормализует код счета, BeforePost отклоняет закрытый период, а событие таблицы меняет результат следующего запроса. Если считать эти обработчики одноразовым кодом представления, замена будет выглядеть готовой, но незаметно изменит работу бизнеса.

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

Как бизнес-логика оказалась в формах Delphi?

Бизнес-логика попала в формы Delphi, потому что VCL давала очень короткий путь к работающей программе. Разработчик помещал на форму dataset, TDataSource, несколько элементов управления данными и кнопку, а решение записывал рядом с событием, которому оно требовалось. Такой выбор был разумным, пока один разработчик отвечал за приложение, а пользователи работали рядом с базой данных. Годы изменений превратили близость кода в архитектуру.

Затем класс формы накопил несколько обязанностей. Он читает состояние элементов управления, распознает намерение пользователя, применяет правила, начинает транзакции, обновляет datasets, форматирует сообщения и решает, какой экран открыть дальше. Файл .dfm добавляет еще один слой: значения свойств и связи компонентов меняют поведение во время выполнения, хотя их нет в читаемом методе Pascal.

Поэтому число строк занижает масштаб миграции. Unit на 250 строк может зависеть от десятков унаследованных свойств, постоянных полей, actions, общих модулей данных и назначений событий, сохраненных в DFM. Даже внешне пустой TDBEdit пишет через TDataSource в буфер dataset. Его поведение зависит от состояния dataset, событий полей, масок ввода и последующей работы BeforePost.

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

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

Файл формы содержит лишь половину программы

До извлечения кода составьте реестр исполняемого поведения. В него должны войти Pascal, ресурсы DFM, унаследованные формы и объекты базы данных. Поиск одних обработчиков нажатия пропускает автоматический переход в режим редактирования и события жизненного цикла. Чтение одного DFM пропускает обработчики, назначенные во время выполнения.

Начните с механической карты. Для каждой формы запишите все события компонентов и datasets, actions, таймеры, обработчики сообщений и вызовы в другие units. Включите OnCreate, OnShow, OnCloseQuery, OnChange, OnExit, OnClick, OnExecute, BeforeEdit, BeforePost, AfterPost, OnCalcFields и обработчики исключений. Ищите присваивания вроде Button.OnClick :=, потому что некоторые приложения меняют привязки после создания объектов.

Затем добавьте неявные пути. Запишите, какие элементы управления указывают на каждый TDataSource, какой dataset предоставляет каждый источник, включен ли AutoEdit и у каких постоянных объектов TField есть события проверки или изменения. Документация Embarcadero называет TDataSource проводником между dataset и элементами управления данными. Скромное слово «проводник» предупреждает о риске: нажатие клавиши может пересечь границу интерфейса до запуска любой кнопки сохранения.

Составьте таблицу, где каждая строка описывает наблюдаемое поведение, а не метод. Эти столбцы раскрывают большинство ловушек:

Trigger              Reads                    Writes           Rule owner
btnPostClick         invoice fields, role     invoice status   posting policy
AmountFieldValidate  amount, currency         record buffer    money rule
CustomerDataChange   current customer         filter params    query orchestration

Столбец Evidence требует точности. Имя обработчика ничего не доказывает. Сохраните входные значения, состояние dataset, вызовы SQL, возвращенные строки, сообщения и итоговые записанные значения. Если обработчик зависит от порядка событий VCL, зафиксируйте этот порядок. Последовательность остается частью поведения, пока вы не докажете, что пользователи и интеграции ее не наблюдают.

Унаследованные формы требуют отдельного прохода. Дочерний DFM может переопределить свойство и унаследовать событие от предка из другого каталога проекта. Дочерний unit выглядит безобидно, хотя базовая форма открывает datasets или меняет права в OnShow. Разверните цепочку наследования и запишите фактическую конфигурацию компонентов в собранном приложении, а не текст одного файла.

Actions тоже скрывают повторное использование. Один TAction.OnExecute может запускаться пунктом меню, кнопкой панели и сочетанием клавиш, а OnUpdate определяет доступность из глобального состояния. Если новый клиент скопирует лишь видимую кнопку, пользователи клавиатуры потеряют способ запуска, а авторизация превратится в декоративный признак отключения. Проверяйте право выполнения на границе команды, а состояние доступности показывайте как отображение этого решения.

Сначала извлеките решения, потом переносите элементы управления

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

Предположим, форма заказа принимает кредитное решение внутри btnApproveClick. Исходный обработчик читает поля, проверяет признак клиента, сравнивает суммы, обновляет элементы управления, выполняет post dataset и показывает сообщение. Отделите решение от этих эффектов:

type
  TApprovalInput = record
    OrderTotal: Currency;
    CreditLimit: Currency;
    AccountOnHold: Boolean;
  end;

  TApprovalDecision = record
    Allowed: Boolean;
    ReasonCode: string;
  end;

function DecideApproval(const Input: TApprovalInput): TApprovalDecision;
begin
  if Input.AccountOnHold then
    Exit(TApprovalDecision.Create(False, 'ACCOUNT_HOLD'));
  if Input.OrderTotal > Input.CreditLimit then
    Exit(TApprovalDecision.Create(False, 'LIMIT_EXCEEDED'));
  Result := TApprovalDecision.Create(True, 'APPROVED');
end;

Точный синтаксис record, возможно, придется подстроить под версию Delphi в системе. Важен сам дизайн: функция принимает значения, возвращает решение и ничего не знает о TEdit, TField, модальных результатах или транзакциях. Ее покрывает модульный тест, пакетный импорт вызывает аналогичную операцию, а целевой сервис реализует тот же контракт.

Не переносите старый обработчик целиком в класс TOrderService. Метод, который принимает форму или обращается через глобальный модуль данных, сохраняет связь с интерфейсом. Двадцать элементов управления в параметрах лишь прячут эту связь в сигнатуре. Описывайте входные данные в терминах бизнеса и передавайте достаточно контекста для однозначного решения.

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

Элементы управления данными создают невидимый путь записи

Для элементов управления данными в новой системе нужна явная модель редактирования, потому что они объединяют показ, навигацию, буферизацию, проверку и сохранение. Поле браузера, связанное с JSON, не равно TDBEdit, который подключен через TDataSource к активному TDataSet.

Первое скрытое поведение связано с переходом в режим редактирования. Embarcadero пишет, что TDataSource.AutoEdit по умолчанию равен true и вызывает метод Edit dataset, когда пользователь пытается изменить связанный элемент. Поэтому исходное приложение может заблокировать строку, пометить запись измененной или включить действия Post и Cancel при первом нажатии клавиши. Новый интерфейс, который ждет явного сохранения, использует другую модель конкуренции, даже если поля выглядят одинаково.

Второе скрытое поведение связано с буфером. Показанное значение поля может не совпадать ни с последним зафиксированным значением в базе, ни со значением, которое увидит другой элемент после события. Edit, Insert, Post, Cancel, кэшированные обновления и настройки provider определяют момент сохранения изменений. Запишите эти состояния как небольшую машину состояний. Например: View разрешает навигацию; Edit хранит локальный черновик; Saving проверяет черновик и отправляет одну команду; Conflict сохраняет черновик пользователя и показывает более новую серверную версию.

Третье поведение касается места проверки. Маска ввода проверяет символы во время набора. Событие поля OnValidate проверяет полное значение перед записью в буфер записи. BeforePost может проверить всю запись. Ограничения базы срабатывают позже и охватывают связи, о которых форма не знает. Документация Embarcadero по TField.OnValidate прямо говорит, что программное присваивание обходит EditMask, а OnValidate все равно проверяет поле до post. Поэтому постоянные правила стоит перенести ниже уровня виджета.

При переносе разделите проверки на четыре группы:

  1. Помощь при вводе, включая форматирование и быструю реакцию на символ, остается в клиенте.
  2. Инварианты поля, например допустимый набор кодов, живут в доменной операции и могут повторяться в клиенте ради быстрой реакции.
  3. Межполевые правила и проверка полномочий выполняются на сервере или границе приложения, которой принадлежит команда.
  4. Postgres продолжает обеспечивать ссылочную целостность и уникальность, даже если раньше выполняются более понятные проверки.

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

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

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

События dataset не образуют доменную модель

Не ограничивайтесь копией экранов
Архитектура меняется, а проверка паритета сохраняет исходное бизнес-поведение Delphi.

Перенос datasets в TDataModule улучшает организацию, но сам по себе не отделяет бизнес-логику. Embarcadero описывает TDataModule как место для централизации невизуальных компонентов и даже допускает там бизнес-правила. Совет помогает разгрузить формы. Он не создает границ, явных входных данных или независимо тестируемых сценариев.

События dataset часто смешивают три задачи. BeforePost может проверить инвариант, заполнить поля аудита и выполнить другой запрос. AfterScroll может обновить detail-dataset и включить action. OnCalcFields может вычислить значение для показа, которое другой обработчик позже считает окончательным. Копирование этих событий в repository или hook ORM воспроизводит ту же неоднозначность.

Классифицируйте каждое событие по причине запуска и гарантии. Доменная операция должна выполняться, потому что вызывающая сторона запросила ApproveOrder, а не потому что случайный dataset сделал post. Repository должен сохранить одобренный заказ, а не решать, разрешено ли одобрение. Модель представления может вычислить текст для экрана, но сохраняемые суммы должны приходить из правила, которое отвечает за денежные расчеты.

Есть неудобный случай: сторонний код может напрямую вызвать Post и полагаться на защиту в BeforePost. Не удаляйте эту защиту во время извлечения. Поместите правило в вызываемый unit, вызывайте его из BeforePost и направляйте новые команды через то же правило. Убирайте старое событие лишь после того, как трассировка подтвердит новый путь для каждой записи.

Глобальные модули данных требуют особой осторожности. Форма может предполагать, что dmMain.qryCustomer уже открыт, установлен на того же клиента и входит в транзакцию, начатую в другом месте. Это общее изменяемое состояние. Выразите предпосылки через явные идентификаторы и области транзакций. Передать CustomerId безопаснее, чем текущую строку dataset, курсор которого может сдвинуть другое событие.

Транзакции должны следовать за сценарием

Границы транзакции должны охватывать бизнес-операцию, а не обработчик кнопки или каждый post dataset. Старый код может начать транзакцию в одном событии, изменить несколько datasets через вложенные вызовы и выполнить commit в другом событии. Разделение такой последовательности между HTTP-запросами оставит частичные изменения, которых настольное приложение никогда не допускало.

Проследите одну успешную операцию и каждый значимый сбой. Запишите SQL, вызовы хранимых процедур, созданные идентификаторы, блокировки и точку commit или rollback. Затем назовите операцию на языке бизнеса. ClosePeriod может обновить запись периода, создать проводки и отклонить ожидающие черновики. Эти изменения относятся к одной команде приложения, даже если VCL достигает их через три формы.

Определите запрос и результат до выбора деталей транспорта:

{
  "operation": "ApproveOrder",
  "order_id": 4812,
  "expected_version": 17,
  "actor_id": 204,
  "decision_input": {
    "order_total": "1250.00",
    "currency": "EUR"
  }
}

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

Не публикуйте общий endpoint UpdateOrder, который принимает все столбцы. Он переносит абстракцию dataset через сеть и позволяет вызывающим сторонам создавать состояния, которые раньше запрещала форма. Команды ApproveOrder, ReleaseHold и ChangeDeliveryDate выражают намерение и задают обоснованную границу транзакции.

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

Характеризационные тесты станут первой спецификацией

Сохраните правила datasets полностью
Вся база кода читается вместе, включая обработчики форм, datasets и связанные языки.

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

Если правила компании разрешают, запишите представительный производственный трафик, удалите или защитите чувствительные данные и превратите каждую операцию в воспроизводимый случай. Для настольного приложения трафик не ограничивается сетевыми запросами. Запишите начальное состояние базы или стабильный fixture, ввод пользователя, нужные права, вызванное действие, сообщения или коды причин, изменения SQL и итоговые строки. Добавьте отмену, двойные нажатия, устаревшие записи, null, границы округления и сбои базы.

Компактный fixture облегчает проверку:

case: approve-order-over-limit
given:
  order_id: 4812
  order_total: "1250.00"
  credit_limit: "1000.00"
  account_on_hold: false
when: ApproveOrder
expect:
  allowed: false
  reason_code: LIMIT_EXCEEDED
  order_status: DRAFT
  committed_writes: 0

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

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

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

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

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

Прямой перенос интерфейса сохраняет дорогую границу

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

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

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

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

Для большинства долгоживущих систем стройте цель вокруг команд, запросов и явного состояния. Сервисы Go подходят для транзакционных операций приложения и доступа к данным. Rust уместен для числовых ядер, где точное поведение и производительность требуют узкой границы. Клиенты TypeScript должны владеть состоянием взаимодействия и логикой показа, а Postgres обеспечивает постоянные реляционные ограничения. Эти варианты указаны в контексте проекта, но не каждому наследию Delphi нужны все четыре.

Переключайте операции, а не формы

Задайте границы элементам данных
Переписывание отделяет взаимодействие TypeScript от полномочных правил сервиса и базы.

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

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

Практическая последовательность состоит из пяти частей:

  1. Запишите текущее поведение и сбои выбранной операции.
  2. Извлеките входы правил и коды результатов, пока форма еще отвечает за представление.
  3. Поставьте сохранение за адаптером и определите границу транзакции.
  4. Воспроизведите случаи паритета на обеих реализациях и классифицируйте каждое различие.
  5. Направьте контролируемую часть вызовов на новый путь и добавьте явный переключатель отката.

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

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

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

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

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

Новая система должна исключить скрытое состояние

Цель готова, когда бизнес-решения больше не зависят от элемента управления, текущей строки dataset, порядка создания форм или неявной глобальной транзакции. Операция должна принимать сериализованный ввод, возвращать стабильный результат и тестироваться без создания окна.

Этот критерий обнаруживает незавершенное извлечение. Сервис, который читает Screen.ActiveForm, repository, который выполняет правило в общем hook сохранения, или клиент TypeScript, который рассчитывает окончательную сумму счета, сохраняет старую проблему. Имена изменились, но поведение осталось скрытым за событиями инфраструктуры.

Используйте при проверке кода короткий список границ:

  • Принимает ли операция бизнес-значения и идентификаторы вместо элементов управления или курсоров dataset?
  • Выполняются ли все постоянные правила для вызовов из интерфейса, импорта и API?
  • Охватывает ли одна транзакция полное бизнес-изменение?
  • Описаны ли явно результаты конкуренции и повторов?
  • Доказывает ли записанный случай паритет без сравнения пикселей?

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

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

Вопросы

Как понять, что обработчик события Delphi содержит бизнес-логику?

Представьте вызов той же операции из импорта или API без создания формы. Решения, которые должны по-прежнему действовать, относятся к бизнес-правилам; смена фокуса, диалоги и компоновка остаются представлением.

Стоит ли сначала перенести код формы в TDataModule?

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

Безопасно ли заменять элементы VCL для данных обычными веб-полями?

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

Где должны находиться правила Delphi OnValidate в новой системе?

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

Как безопасно перенести логику из BeforePost?

Извлеките правило в вызываемый unit и продолжайте вызывать его из BeforePost, пока существуют старые пути записи. Удаляйте событие лишь после подтверждения новой границы для каждой записи трассировкой.

Почему прямой перенос интерфейса Delphi обходится так дорого?

Он перестраивает экраны до обнаружения операций и неявного состояния под ними. Команда платит за визуальное воспроизведение, а затем за изменения интерфейса и API, которых требуют скрытые правила.

Нужно ли сохранять каждый дефект ради паритета поведения?

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

Могут ли модульные тесты заменить записанные производственные случаи?

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

Стоит ли публиковать в новом API общие CRUD-endpoints?

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

Какую единицу безопаснее всего переключать при миграции Delphi?

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