Проблема legacy-кода в Bitrix редко состоит в возрасте файла. Старый вызов может держать неявные значения, порядок хуков, формат ошибок и поля, которые читает соседний модуль. Если заменить его только потому, что новый API выглядит аккуратнее, цена проявится позже: потеряется поведение, а исправлять его придётся уже по косвенным симптомам. Поэтому первый вопрос — не «как переписать», а «какой контракт нельзя сломать».
Решение удобно разделить на три действия: сохранить, обернуть или заменить. Сохранить — значит оставить вызов и зафиксировать его наблюдаемое поведение. Обернуть — поставить адаптер между legacy API и остальным кодом, чтобы callers перестали знать детали. Заменить — удалить старую границу только после того, как новый контракт описан тестами. Это не догма: выбор зависит от числа callers, побочных эффектов и качества проверки.
Сначала отделяем возраст от риска
Класс CUser относится к старому API Bitrix, но само имя класса не говорит, что его можно безопасно удалить. Документация перечисляет поля и методы, а также указывает аналог в D7. Для проекта этого мало: нужно увидеть, какие поля реально передаются, какие значения возвращаются и что вызывается после сохранения. Пока это не известно, сохранение или узкий wrapper дешевле полной миграции.
Риск растёт, если один вызов смешивает несколько задач. Например, функция может одновременно нормализовать email, создавать пользователя, запускать событие и возвращать ID. Переписать её на новый класс без раздельных тестов значит поменять четыре контракта за один commit. Адаптер позволяет сначала выделить форму данных и ошибку, а уже потом менять внутреннюю реализацию.
| Действие | Когда подходит | Цена | Критерий перехода |
|---|---|---|---|
| Сохранить | побочные эффекты не описаны, callers мало | остаётся старый долг | характеризующие тесты и список полей |
| Обернуть | callers несколько, контракт можно выделить | появляется адаптер | внешний код видит canonical input/output |
| Заменить | новый контракт и тесты готовы | нужен полный regression | старый путь больше не нужен |
| Остановиться | нет версии, полей или воспроизводимого результата | изменение откладывается | собраны факты о границе |
Учебная функция выбора границы
Функция ниже не изображает Bitrix runtime. Она показывает рабочую механику решения: входом являются количество callers, наличие characterization tests, неизвестные побочные эффекты и canonical contract. Возвращается одно действие и причина. Эти поля можно заполнить из code search, тестов и документации, а не из ощущения «код старый».
import { chooseLegacyBoundary } from './upgrade-2027-02.mjs';
const cases = [
{ callers: 1, characterizationTests: false, unknownSideEffects: true, canonicalContract: false },
{ callers: 4, characterizationTests: true, unknownSideEffects: false, canonicalContract: false },
{ callers: 4, characterizationTests: true, unknownSideEffects: false, canonicalContract: true },
];
for (const item of cases) console.log(chooseLegacyBoundary(item));
// keep -> сохранить наблюдаемое поведение и добавить тесты
// wrap -> несколько callers требуют одной адаптационной границы
// replace -> новый контракт проверен тестами и отделён от legacy APIПервый результат намеренно не предлагает «сразу новый класс»: неизвестные эффекты сильнее желания обновиться. Второй показывает пользу wrapper, когда callers несколько. Третий разрешает замену только при наличии canonical contract. В рабочем репозитории эту функцию заменит ADR или короткая карточка изменения, а значения подтвердят тесты и просмотр вызовов.
Что должен скрывать адаптер
Адаптер не должен превращаться в копию всего legacy API. Он принимает только нужные поля, проверяет обязательные значения и переводит ошибку в понятный тип. Например, внешняя функция может принимать { email, phone }, а внутри временно собирать массив полей для CUser::Update. Так callers перестают зависеть от названий PERSONAL_PHONE и от способа загрузки модуля.
Нужно заранее решить, кто владеет преобразованием. Если один caller исправляет телефон, второй передаёт его как есть, а третий пишет пустую строку, wrapper не создал контракт — он спрятал разнобой. Входная нормализация должна быть в одном месте, а правила обратного преобразования — рядом с ней. В таблице изменений укажите поле, источник, допустимую пустоту и способ проверить результат.
Действия по порядку
- Найти все callers legacy-функции и выписать фактические поля, значения по умолчанию и обработку ошибок.
- Проверить, подключён ли нужный Bitrix-модуль, и зафиксировать установленную версию вместе с документацией API.
- Добавить characterization tests на текущий результат: успешное сохранение, пустое поле, повторный вызов и ошибку.
- Выбрать keep, wrap или replace; для wrapper описать canonical input/output и список скрываемых legacy-деталей.
- После изменения повторить те же проверки и отдельно проверить события, права и формат возвращаемого ID.
Ограничения и следующий шаг
Без доступа к конкретной установке нельзя обещать совместимость версии, поведение событий или одинаковые тексты ошибок. Документация Bitrix описывает API, но не локальные обработчики и не поля, добавленные проектом. Полная замена особенно опасна, если legacy-вызов участвует в транзакции, импортирует данные или используется административной формой.
Следующий шаг — взять одну функцию с двумя callers и оформить её canonical контракт. Если тесты не удаётся написать без сложной среды, это сигнал оставить код на месте и сначала сократить границу побочных эффектов. Такая остановка тоже инженерное решение: она сохраняет обратимость и делает следующий commit проверяемым.
Проверяемые источники
- Главный модуль: CUser — документация 1С-Битрикс — версия и дата: документация API, проверена 31 July 2026; класс доступен с версии 3.0.6. Применение: Названия CUser, поля и наличие аналога UserTable сверены с документацией Bitrix. Граница: Документация не описывает локальные события, права, обработчики и фактический набор callers проекта.
- CModule::IncludeModule — документация 1С-Битрикс — версия и дата: документация API, проверена 31 July 2026; CModule с версии 3.0.1. Применение: Проверка подключения модуля до вызова API используется как отдельное условие адаптера. Граница: Страница не подтверждает, что нужный модуль установлен в конкретной среде.
- PHP Manual: filter_var — версия и дата: PHP Manual, актуальная страница, проверена 31 July 2026. Применение: PHP-проверка входных значений упомянута как часть нормализации boundary. Граница: Manual не определяет Bitrix-поля и не заменяет тесты проекта.