Legacy-приложение на Objective-C можно безболезненно развивать дальше, если сначала привести в порядок основу: сборку, зависимости, архитектурные границы, тесты и правила изменения кода. Самая частая ошибка — сразу начинать переписывать экраны, хотя сначала нужно сделать кодовую базу управляемой и предсказуемой. На практике, особенно в PropTech, где приложение для риэлтора должно стабильно работать на показе, а синхронизация каталогов объектов идёт в реальном времени, хаотичный рефакторинг без подготовки гарантированно приводит к регрессиям в самые неподходящие моменты.
Зачем вообще готовить legacy-код перед новыми фичами
Старое iOS-приложение обычно страдает не от одного большого дефекта, а от набора мелких проблем: длинные методы, тесные связи между экранами и сервисами, устаревшие зависимости, слабое покрытие тестами и опасные изменения памяти после миграций между manual retain/release и ARC. В контексте недвижимости это выливается в конкретные бизнес-риски: если приложение для агента падает при открытии карточки объекта или теряет данные офлайн-бронирования, страдает не только пользовательский опыт, но и репутация агентства, а иногда и сама сделка.
Если просто добавлять новые функции поверх такого кода, каждая доработка начинает стоить дороже: растет риск регрессий, тормозится выпуск, а команда боится трогать «хрупкие» места. В одном проекте для застройщика мы наблюдали, как добавление простого фильтра по цене в каталог квартир вызывало каскадное падение экранов из-за неявной зависимости от синглтона, который хранил состояние фильтров в ручном режиме управления памятью.
С чего начать: короткий аудит кодовой базы
Перед рефакторингом нужен не «идеальный» реестр проблем, а практичная карта рисков. Начинать лучше с четырех вещей: сборка, краш-статистика, самые изменяемые модули и самые дорогие по времени участки. Когда мы аудировали приложение для риэлторской сети, первым делом обнаружили, что проект собирается только на конкретной версии Xcode с устаревшим CocoaPods, а треть зависимостей уже deprecated и конфликтует с iOS SDK. Без исправления этого фундамента любой рефакторинг был бы обречён.
Что проверить в первую очередь
- Проект собирается на текущем Xcode и актуальном iOS SDK.
- Есть ли предупреждения компилятора, которые уже стали нормой и скрывают реальные ошибки.
- Какие экраны или сервисы меняли чаще всего за последние релизы.
- Где чаще всего возникают крэши, зависания и утечки памяти — особенно в сценариях, связанных с загрузкой изображений планировок или синхронизацией больших JSON-каталогов.
- Какие сторонние библиотеки давно не обновлялись.
- Есть ли завязка на устаревшие API, которые уже подводят при обновлении iOS.
Практичный результат аудита
После аудита должен получиться короткий список:
- что мешает выпускать релизы сейчас;
- что опасно трогать — например, модуль кэширования изображений с ручным retain/release, который держит в памяти десятки мегабайт фотографий объектов;
- что можно отделить и переписать первым — часто это сервис авторизации или парсер ответа от CRM недвижимости;
- что лучше оставить как есть до появления тестов и изоляции.
Как понять, что код уже пора модернизировать
Ниже — признаки, которые почти всегда говорят, что приложение готово к поэтапной подготовке к дальнейшей разработке. В PropTech-решениях эти симптомы обостряются, потому что бизнес-логика часто завязана на жёсткие сроки и высокую стоимость ошибки.
| Признак | Что это значит на практике | Риск |
|---|---|---|
| Большие view controller’ы | UI, бизнес-логика и работа с сетью смешаны в одном месте — например, контроллер карточки квартиры сам делает запрос к API, маппит JSON и форматирует цену | Любое изменение ломает соседние сценарии |
| Повторяющийся код | Одинаковые проверки, форматирование, запросы и маппинг размазаны по проекту — типично для каталогов, где каждый экран списка объектов дублирует логику пагинации | Высокая стоимость поддержки |
| Слабые тесты | Нет быстрого способа проверить, что поведение не изменилось — особенно опасно для сценариев бронирования или расчёта ипотеки | Регрессии после каждой правки |
| Устаревшие библиотеки | Зависимости конфликтуют с новым SDK или Xcode — например, старая версия AFNetworking не поддерживает ATS, а без неё не пройти аудит безопасности для банковского модуля ипотеки | Блокировка обновлений |
| Много ручного memory management | Повышенный риск утечек, dangling pointers и неожиданных циклов удержания — часто встречается в модулях работы с изображениями и офлайн-хранилищами объектов | Сложнее безопасно вносить изменения |
| Монолитная структура | Модули зависят друг от друга почти без границ — например, экран поиска объектов напрямую дёргает методы из контроллера карточки | Нельзя менять по частям |
Базовый план подготовки legacy-приложения
Лучше идти не по принципу «переписать всё», а по цепочке, где каждый шаг уменьшает риск следующего. В проектах для рынка недвижимости это особенно важно, потому что приложение часто обслуживает одновременно и риэлторов, и клиентов, и любая остановка релизов бьёт по бизнесу.
1. Сделать сборку воспроизводимой
Первый шаг — убедиться, что проект стабильно собирается в чистом окружении. Если сборка зависит от «магии» локальной машины, дальнейшая работа будет ломаться на ровном месте. В одном кейсе приложение для девелопера собиралось только на MacBook конкретного разработчика, потому что часть путей к библиотекам была прописана абсолютно. Перенос в CI выявил десяток скрытых проблем.
Что стоит сделать:
- зафиксировать версии Xcode и инструментов;
- убрать неиспользуемые схемы и мертвые target’ы;
- проверить конфигурации Debug/Release;
- описать сборку в CI;
- зафиксировать зависимости и порядок их установки (например, через Podfile.lock и строгую версионность).
2. Добавить минимальный уровень автоматических тестов
Не нужно покрывать весь проект сразу. Достаточно начать с «страховочных» тестов на самые критичные сценарии: авторизация, список объектов, карточка объекта, поиск, офлайн-режим, синхронизация. Для агентского приложения, где риэлтор офлайн просматривает каталог и бронирует квартиры, тесты на сохранение и восстановление данных после потери сети — это не роскошь, а необходимость.
Полезный ориентир:
- модульные тесты — для логики и преобразований данных (например, расчёт комиссии или форматирование площади);
- интеграционные — для связки сервисов и моделей (получение списка объектов из API и маппинг в локальное хранилище);
- UI-тесты — только для ключевых пользовательских путей (поиск объекта, открытие карточки, отправка заявки).
3. Сначала выделить границы, потом переписывать
Если код перемешан, сначала нужно отделить ответственность. В Objective-C это особенно важно: старые приложения часто строились по принципу «все в контроллере». Гораздо безопаснее сначала вынести логику в отдельные классы, чем сразу менять технологический стек. Например, в проекте для управления арендой мы выделили сервис синхронизации с календарём бронирований в отдельный класс, и только после этого смогли безболезненно обновить сетевой слой.
Какие зоны стоит рефакторить первыми
Приоритизация решает больше, чем объем работы. Если начать с самого «грязного» кода, можно увязнуть на недели, не получив ощутимого бизнес-эффекта. Лучше выбирать участки, которые одновременно и часто меняются, и несут высокие риски.
Лучшие кандидаты на первый рефакторинг
- Длинные методы, которые сложно читать и тестировать — например, метод обновления UI после ответа сервера, где вперемешку парсинг, валидация и форматирование.
- Классы, которые делают слишком много всего сразу — контроллер, который и загружает данные, и управляет кэшем изображений, и считает ипотеку.
- Дублирующийся код в нескольких экранах — типично для каталогов, где каждый список объектов повторяет логику сортировки и фильтрации.
- Сетевой слой без единого формата ошибок — когда каждый запрос возвращает ошибку по-своему, и UI не может унифицировать показ алертов.
- Логика форматирования, вычислений и преобразования данных, спрятанная в UI — например, форматирование цены в рублях прямо во view.
- Код, завязанный на deprecated API — особенно критично для работы с геолокацией или картами, которые часто меняются в iOS.
- Места с повторяющимися багами — если экран списка объектов регулярно падает при скролле, проблема скорее всего в общем механизме повторного использования ячеек и управления памятью.
Что обычно даёт быстрый эффект
- extract method — разбить большой метод на маленькие;
- extract class — вынести отдельную ответственность (например, парсер ответа API в отдельный объект);
- move method / move field — перенести поведение туда, где ему место;
- introduce constant — заменить магические числа и строки (идентификаторы типов недвижимости, ключи JSON);
- simplify conditionals — упростить вложенные условия, которые часто возникают при проверке доступности объектов в разных статусах.
ARC, memory management и что делать со старым retain/release
Если проект пережил несколько эпох Objective-C, в нём часто смешаны старые паттерны управления памятью и более современный код. Здесь главная цель — не «перевести всё сразу», а убрать самые опасные места и убедиться, что поведение не изменилось. В PropTech-приложениях утечки памяти особенно коварны: при длительном использовании (риэлтор может держать приложение открытым весь день) даже небольшая утечка в кэше изображений планировок или в подписке на уведомления о новых объектах приводит к вылету по memory pressure в самый неподходящий момент.
Что проверить
- есть ли участки с ручным retain/release/autorelease;
- встречаются ли циклы удержания между контроллерами, блоками и делегатами — классика: контроллер карточки объекта сильно ссылается на блок, а блок захватывает контроллер;
- корректно ли используются weak-ссылки — особенно в блоках обработки ответов сети;
- нет ли утечек в кешах, singleton’ах и подписках на уведомления — например, синглтон менеджера объектов недвижимости может накапливать словари с объектами, которые уже не нужны;
- не возвращает ли код освобожденные объекты через старые паттерны — такое бывает в самописных кэшах, где после очистки памяти объект может быть пересоздан, но указатель остаётся висячим.
Безопасный подход
- сначала включить анализ утечек и предупреждений (статический анализатор Xcode);
- затем локально мигрировать отдельные модули на ARC — например, модуль работы с избранными объектами, который уже покрыт тестами;
- после каждой правки прогонять тесты и запускать сценарии с памятью под нагрузкой — симулировать долгую работу с приложением, открытие множества карточек, переключение между разделами;
- не смешивать крупный рефакторинг памяти с одновременной архитектурной перестройкой — слишком высок риск потерять контроль над побочными эффектами.
Как выстроить архитектуру, если приложение уже выросло
Legacy-приложение редко становится проще само. Если продолжать писать в стиле «новый экран — новый массив if’ов», со временем вся кодовая база превращается в набор взаимозависимых файлов. В мире недвижимости это приводит к тому, что добавление нового типа объекта (например, паркинга или кладовки) требует правок в десятке мест, от контроллера списка до модуля расчёта платежей.
Рабочая цель
Не обязательно сразу переходить на сложную «идеальную» архитектуру. Достаточно добиться трех вещей:
- у UI нет прямого доступа к тяжелой бизнес-логике — контроллер не должен знать, как именно вычисляется ипотечный платёж или проверяется доступность квартиры;
- сетевой слой отделен от представления — запросы и парсинг ответов не лежат в view controller’ах;
- состояние экрана формируется в одном месте, а не размазывается по всему контроллеру — например, отдельный объект, который собирает данные для отображения карточки объекта из разных источников.
Практический ориентир для Objective-C
- оставить UIKit там, где он уже стабилен — не стоит трогать работающие экраны без крайней необходимости;
- постепенно выносить бизнес-логику в отдельные сервисы и модели — сервис объектов недвижимости, сервис бронирования, сервис расчёта комиссии;
- для новых модулей вводить более строгие границы — например, модуль виртуальных туров или AR-просмотра квартиры сразу проектировать с чистыми протоколами и dependency injection;
- не тащить новые фичи в старые монолитные контроллеры без необходимости — если нужно добавить ипотечный калькулятор, лучше создать отдельный модуль и интегрировать его через протокол, а не раздувать существующий контроллер карточки.
Как безопасно вводить Swift в Objective-C-проект
Если дальнейшая разработка будет идти в смешанном стеке, лучший путь — не переписывать весь проект, а подключать Swift точечно, по новым модулям. В PropTech это хорошо ложится на типовые сценарии: старый код на Objective-C продолжает обслуживать каталоги и синхронизацию, а новые фичи, вроде чата с агентом или интерактивных планировок, пишутся на Swift.
Хороший сценарий
- новый функциональный блок пишется на Swift — например, модуль электронной подписи договора;
- Objective-C продолжает обслуживать старый стабильный код — все экраны списков и карточек объектов;
- мост между языками держится тонким и понятным — через bridging header и протоколы, без сложных обёрток;
- критические сценарии тестируются на обеих сторонах стека — особенно важно для сквозных процессов, вроде оформления сделки, где участвуют и старые, и новые модули.
Когда не стоит торопиться
- если в проекте нет тестов — любое изменение в смешанной кодовой базе без тестовой подушки становится рискованным;
- если модуль и так нестабилен — сначала стабилизируем, потом переписываем;
- если команда не умеет сопровождать mixed codebase — нужно сначала нарастить экспертизу, иначе количество багов на стыке языков может перевесить выгоду;
- если нужно срочно выпускать релизы без риска — в горячие периоды (например, запуск продаж нового корпуса) лучше отложить языковые миграции.
Зависимости, сборка и обновления SDK
Устаревшие библиотеки — частая причина, по которой legacy-проект перестает нормально жить на новых версиях iOS. В приложениях для недвижимости это может быть критично: если библиотека для работы с картами не обновляется, а Apple требует использовать новую версию MapKit, приложение просто не пройдёт ревью.
Что делать с зависимостями
- составить список всех third-party SDK;
- отметить, какие из них поддерживаются — и какие уже заброшены;
- проверить, какие библиотеки тянут deprecated API — например, старый способ запроса геолокации;
- удалить дублирующие решения — нередко в проекте одновременно живут две библиотеки для работы с изображениями;
- закрепить способ управления зависимостями и версионность — CocoaPods, Swift Package Manager или Carthage, но единообразно;
- не обновлять все сразу, а двигаться по одной цепочке изменений — сначала обновить сетевую библиотеку, убедиться, что ничего не сломалось, затем следующую.
Чего избегать
- массового обновления пакетов перед релизом;
- замены менеджера зависимостей без причины — переход с CocoaPods на SPM только ради моды может отнять недели и принести новые проблемы;
- одновременного рефакторинга сети, UI и storage — слишком много переменных, сложно локализовать источник бага.
Чек-лист готовности legacy-приложения к дальнейшей разработке
Используйте этот список как минимальный критерий «можно развивать дальше». Для приложения, которое ежедневно используют сотни риэлторов, каждый пункт — это страховка от потери данных или срыва показов.
- Проект стабильно собирается в чистом окружении.
- Есть CI-сборка и базовые автотесты.
- Критичные пользовательские сценарии покрыты тестами.
- Известны самые рискованные модули.
- Архитектурные границы хотя бы частично выделены.
- Убраны самые опасные memory-проблемы.
- Устаревшие зависимости зафиксированы и описаны.
- Новые фичи можно добавлять без изменения половины приложения.
- Команда понимает, какие части системы трогать нельзя без подготовки.
Типовые ошибки при подготовке legacy Objective-C-приложения
1. Переписывать вместо рефакторинга
Если бизнес-функция уже работает, чаще выгоднее стабилизировать и постепенно выносить куски кода, чем устраивать большой rewrite. Полная переписка модуля каталога объектов с нуля может занять месяцы, а за это время бизнес потребует новые фильтры и интеграции — и старый код продолжит обрастать костылями.
2. Сначала менять технологию, потом искать тесты
Без тестов миграция превращается в гадание. Даже простые проверки дают больше пользы, чем «красивый» новый слой без защиты. Видел проект, где решили перевести сетевой слой на Swift, не имея ни одного интеграционного теста — в результате половина запросов к CRM недвижимости падала с ошибками маппинга, и релиз откатили.
3. Лечить симптом, а не причину
Если падает конкретный экран, проблема может быть не в нём, а в общем сервисе, модели или потоке данных. Например, экран списка объектов может вылетать из-за того, что сервис синхронизации возвращает битые данные при плохом соединении, а не из-за ошибки в самом контроллере.
4. Делать все сразу
Legacy-код плохо переносит крупные одномоментные изменения. Лучше маленькие законченные шаги с понятным эффектом. Попытка одновременно обновить все зависимости, мигрировать на ARC и переделать архитектуру обычно заканчивается тем, что приложение перестаёт собираться на недели, а команда теряет веру в успех.
Пошаговый план на 30 дней
Первая неделя
- собрать информацию о сборке, зависимостях и крашах;
- зафиксировать проблемные модули;
- определить топ-5 сценариев, которые нельзя сломать (например, просмотр карточки объекта, отправка заявки на бронирование, офлайн-доступ к избранному).
Вторая неделя
- привести проект к воспроизводимой сборке;
- добавить базовый CI;
- написать первые тесты на критические сценарии — хотя бы модульные на ключевую логику и один интеграционный на сквозной сценарий.
Третья неделя
- выделить один наиболее рискованный модуль — например, сервис синхронизации каталога;
- вынести из него часть логики — отделить парсинг ответа от сохранения в БД;
- сократить связность между UI и бизнес-правилами — контроллер списка объектов не должен знать, как именно обновляется локальное хранилище.
Четвертая неделя
- обновить одну зависимость или один слой — например, сетевую библиотеку или модуль кэширования изображений;
- проверить регрессии — пройтись по критическим сценариям вручную и прогонять автотесты;
- задокументировать правила для следующих изменений — какие модули трогать только с тестами, какие зависимости обновлять только после проверки совместимости.
Вывод
Подготовка legacy-приложения на Objective-C к дальнейшей разработке — это не разовая «уборка», а создание безопасной среды для будущих изменений. Сначала нужно сделать сборку воспроизводимой, добавить тестовую защиту, отделить ответственность модулей, снизить риски памяти и только потом двигаться к более крупной модернизации. В PropTech, где приложение — это рабочий инструмент риэлтора, а не просто витрина, такой подход окупается стабильностью релизов и уверенностью, что новая функция не обрушит продажи.
Если приложение уже живет в продакшене, лучший путь почти всегда один: не ломать основу, а поэтапно превратить хаотичный код в систему, которую можно развивать без страха перед каждым новым релизом.
FAQ
С чего начать, если кодовая база очень старая?
С аудита сборки, крашей, зависимостей и самых часто изменяемых модулей. Это даст приоритеты и уберет хаос. Даже в проекте десятилетней давности можно найти точку опоры — обычно это модуль, который работает стабильно, и его можно взять за образец для наведения порядка.
Нужно ли сразу переводить проект на Swift?
Нет. Безопаснее вводить Swift точечно, по новым модулям, сохраняя работающий Objective-C-код. Особенно если в проекте ещё много ручного управления памятью — сначала лучше причесать старый код, а потом аккуратно подключать Swift.
Можно ли подготовить legacy-проект без полной переписки?
Да. Обычно достаточно рефакторинга, тестов, выделения границ и постепенной модернизации зависимостей. Полная переписка — это крайняя мера, когда поддерживать существующий код уже дороже, чем создать новый, но до этого состояния проект лучше не доводить.
Что важнее всего перед дальнейшей разработкой?
Стабильная сборка, базовые тесты и понятные архитектурные границы. Без этого любое изменение будет рискованным. Если вы можете ответить на вопрос «что точно сломается, если я поменяю этот класс?», значит, вы уже на правильном пути.
