惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

雷峰网
雷峰网
宝玉的分享
宝玉的分享
I
InfoQ
P
Privacy International News Feed
V
V2EX
IT之家
IT之家
S
SegmentFault 最新的问题
D
Darknet – Hacking Tools, Hacker News & Cyber Security
V2EX - 技术
V2EX - 技术
C
CERT Recently Published Vulnerability Notes
C
Check Point Blog
The Register - Security
The Register - Security
爱范儿
爱范儿
博客园 - 三生石上(FineUI控件)
AWS News Blog
AWS News Blog
M
MIT News - Artificial intelligence
C
Cyber Attacks, Cyber Crime and Cyber Security
F
Fortinet All Blogs
B
Blog
N
Netflix TechBlog - Medium
B
Blog RSS Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Last Week in AI
Last Week in AI
T
Threatpost
Forbes - Security
Forbes - Security
U
Unit 42
A
Arctic Wolf
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
P
Palo Alto Networks Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recorded Future
Recorded Future
L
Lohrmann on Cybersecurity
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
P
Proofpoint News Feed
月光博客
月光博客
Spread Privacy
Spread Privacy
MongoDB | Blog
MongoDB | Blog
Jina AI
Jina AI
I
Intezer
V
Visual Studio Blog
阮一峰的网络日志
阮一峰的网络日志
The Hacker News
The Hacker News
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
LangChain Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
博客园_首页
MyScale Blog
MyScale Blog
腾讯CDC
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
量子位

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
За два месяца вместо года: как мы переписали 97 тысяч строк кода с Objective-C на Swift
Andrey Sikerin · 2026-05-05 · via Все публикации подряд на Хабре

Средний

9 мин

29K

Миграция большого iOS-проекта с Objective-C на Swift кажется понятной задачей ровно до тех пор, пока не начинаешь считать объём. В нашем случае это были 10 тысяч файлов, сотни тысяч строк кода и постоянная необходимость не останавливать развитие продукта. Ручной подход работал слишком медленно, поэтому мы начали автоматизировать миграцию с помощью LLM — и в итоге превратили её из бесконечного техдолга в воспроизводимый процесс.

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

Меня зовут Андрей Сикерин, я руковожу одной из групп iOS-разработки Яндекс Браузера. Вместе со мной статью писала Елизавета Мазулова@evmazulova, разработчица из нашей же команды. Она создала систему промптов для миграции, ревью, рефакторинга и автоматизации тестирования. Вместе мы расскажем, как устроен весь процесс изнутри: от выбора порядка миграции модулей до контроля качества через тесты.

Все промпты, конфигурационные шаблоны и вспомогательные скрипты выложили в открытый доступ — забирайте и адаптируйте под свой проект. Мы уже проверили, что подход работает не только у нас: взяли открытый репозиторий приложения Wikipedia для iOS и без существенных изменений промптов мигрировали один из его пакетов и поделились результатом с сообществом в виде открытого PR.

Пять лет миграции — и только половина пути

Браузер вышел в 2013 году и был полностью написан на Objective-C, Swift тогда ещё не существовало. Первые Swift-файлы появились в проекте в 2015-м, а в 2020-м произошло объединение кодовых баз Браузера и приложения Яндекс в едином репозитории. Поскольку приложение Яндекс было целиком на Swift, это задало новый вектор: мы объявили мораторий на написание нового кода на Objective-C.

Objective-C тормозит развитие продукта: Swift Concurrency, SwiftUI и другие современные инструменты Apple с ним не дружат, скорость разработки падает. Плюс рынок: найти разработчика, который хорошо знает Objective-C, с каждым годом становится сложнее.

На тот момент в проекте было 10 038 файлов и 541 856 строк Objective-C. Мораторий остановил рост этого кода, но не помог от него избавиться. Миграция шла, но медленно: спустя пять лет, ко второй половине 2025 года, было переписано лишь чуть более 50% кода.

Причины низкого темпа понятны: огромный объём legacy, запутанные зависимости между модулями, постоянная необходимость развивать продукт. К тому же ручное переписывание — монотонная работа, чреватая ошибками, поэтому разработчики берутся за неё неохотно.

Если бы мы продолжали в том же темпе, полная миграция растянулась бы ещё на годы вперёд. Но с появлением LLM стало очевидно, что эта задача — отличный кандидат для автоматизации: уже есть референсное поведение, задача сводится к переводу с одного языка на другой без изменения функциональности. А значит, результат можно проверить автоматически: скомпилировать и прогнать тесты.

Начинать нужно с «листьев» графа зависимостей

LLM отлично знает синтаксис Swift и Objective-C, но ничего не знает о специфике конкретного проекта. Ядро браузера — форк Chromium со своей системой сборки (gn, ninja), собственной библиотекой и строгим code style. Без этого контекста нейросеть генерирует «сферический код в вакууме».

Поэтому первым делом мы структурировали всю необходимую информацию о проекте в конфигурационный файл rules.md: архитектуру и систему сборки, code style, структуру тестов, архитектурный словарь типов и паттернов для замены.

Следующий вопрос: с чего начать? К тому моменту оставалось переписать ~3700 файлов. Мы быстро поняли, что эффективнее всего переписывать целыми модулями — в среднем по 2000–3000 строк. Но случайный выбор модуля легко приводил к проблемам. Если компонент активно используется из Objective-C-кода, при переписывании на Swift приходится создавать bridging-слой (прослойку совместимости между Swift и Objective-C), накапливать @objc-аннотации и наследоваться от NSObject.

Оптимальная стратегия: начинать с «листьев» графа зависимостей, то есть компонентов, которые вызываются только из Swift-кода. При таком подходе можно писать чистый Swift без промежуточных слоёв.

Искать такие модули можно вручную, доверить поиск LLM или автоматизировать процесс. Учитывая огромный размер нашего репозитория, мы выбрали последний вариант и написали скрипт-анализатор. Он строит граф зависимостей, парсит BUILD.gn-файлы и ранжирует компоненты от листьев к вершине.

Система из четырёх промптов

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

  • Swift-conversion — основной промпт переписывания. Определяет оптимальный порядок миграции файлов внутри модуля, заменяет типы и имена, валидирует результат через компиляцию и тесты.

  • Refactoring — улучшение кода под Swift best practices после успешной сборки. Выбор правильных типов (struct/class/enum), упрощение протоколов до замыканий, внедрение dependency injection.

  • Review — автоматическая проверка по чек-листу перед отправкой на ревью. Проверяет заголовки файлов, корректность замены типов, соответствие code style.

  • ObjC-removal — периодическая очистка от избыточных @objc-аннотаций после того, как зависимости мигрированы.

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

Большинство агентских IDE поддерживают механизм пользовательских правил: промпты автоматически подгружаются в контекст каждого диалога. Мы используем SourceCraft Code Assistant (.codeassistant/rules/), но тот же подход работает и в других популярных IDE.

Контроль качества: генерация тестов

Но переписать код — это только полдела, важно убедиться, что мы ничего не сломали.

Многие старые модули исторически не имели достаточного тестового покрытия. Это возлагало колоссальную ответственность на ревьюеров: проверять глазами корректность логики в PR на 2000+ строк кода рискованно и ненадёжно.

Поэтому мы изменили порядок. Для модулей с недостаточным покрытием сначала пишем Swift-тесты на Objective-C-интерфейс и вливаем их отдельным PR — они проходят CI и фиксируют правильное поведение существующего кода. И только после этого открываем второй PR с миграцией: свифтизированный код сразу проверяется уже влитыми тестами. Существующие Objective-C-тесты мигрируем в последнюю очередь.

Генерацию тестов разбили на три этапа:

  • Планирование. LLM анализирует исходный Objective-C-модуль и составляет тест-план на естественном языке: сценарии использования, граничные случаи, ожидаемое поведение.

  • Валидация. Разработчик вычитывает план — это быстрее, чем читать код. Исключаем избыточные проверки, добавляем упущенные сценарии.

  • Генерация. Только после утверждения плана LLM пишет код тестов.

Эти этапы оформлены в отдельный промпт для интеграционных тестов.

Написание тестов — это ещё один пласт задач, с которыми LLM справляется успешно. С таким подходом мы закрываем две потребности сразу: не только безопасно мигрируем код, но и попутно покрываем тестами старые «тёмные пятна» в проекте.

Детали реализации промптов

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

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

Мы написали несколько простых скриптов, которые отрабатывают до компиляции и предотвращают большинство однотипных ошибок ещё «на подлёте».

  • Скрипт для импорта зависимостей анализирует импорты header-файлов в переписываемом модуле и на их основе определяет список необходимых Swift-модулей. Он нужен, чтобы заранее устранять частую ошибку cannot find type 'ClassName' in scope. Конкретная реализация зависит от системы сборки, в нашем случае мы парсим BUILD.gn-файлы.

  • Скрипт для переименования классов обновляет имена классов с Objective-C-префиксами на Swift-версии (YBTabManager → TabManager) во всех Swift-файлах.

  • Скрипт для обновления импортов исправляет Objective-C-код, который после миграции начинает использовать Swift-классы (#import "YBTabManager.h" → #import "Browser-Swift.h").

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

Архитектурный словарь: перевод исторических паттернов

Многие архитектурные решения браузера пришли из мира C++. При миграции на Swift заменяем старые Objective-C-обёртки на современные аналоги из нашей базовой библиотеки. Все правила собраны в макросе objc_to_swift_equivalents().

  • Асинхронность: YBFuture/YBPromise → Future/Promise. Нужно правильно конвертировать типы между старым и новым API: использовать Future.convert(from:) для преобразования и корректно работать с ошибками через Result.

  • Реактивность: YBCondition → ObservableVariable<Bool> — типизированный observable вместо булевого флага.

  • Коллекции наблюдателей: YBObserversSet → WeakCollection, методы add() → append(), enumerateObjects() → forEach().

  • Блоки: YBVoidBlock → Action — тип-алиас для замыканий без параметров и возвращаемого значения.

  • Ленивая инициализация: YBLazyLoader/YCLazy → Lazy.

Каждое правило подкреплено примерами из реального кода, чтобы модель понимала не только что заменять, но и как это выглядит в контексте.

Code style

Помимо замены типов, важно следовать идиоматичным Swift-подходам конкретного проекта. В нашем случае правила описаны в макросе project_code_style().

Безопасность: запрет на force unwrap (!) и fatalError. Даже если оригинальный Objective-C-код гарантировал не-nil значение, используем безопасные альтернативы: optional binding, nil-coalescing operator (??) или assert() для отладки.

Декларативность: использование .build-блоков вместо конкатенации массивов через +. Result Builder позволяет писать более читаемый код с условной логикой и автоматическим flattening вложенных массивов.

Простота: замена convenience init на единый init с дефолтными значениями параметров. Использование lazy var вместо приватных методов-геттеров для кеширования.

Objective-C-совместимость: атрибуты @objc и наследование от NSObject убираются там, где класс больше не используется из Objective-C-кода. Исключения: методы-селекторы для target-action и классы, реализующие Objective-C-протоколы из старого кода.

Маркировка

Каждый переписанный файл получает специальный хедер:

// Generated as part of AI Swiftization.
// Please review, refactor, and ensure it follows the code style before removing this comment.

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

Важность примеров и контекста

Качество конвертации сильно зависит от количества примеров в промпте. Особенно это критично для сложных случаев — например, конвертация YBFuture с одновременной обработкой успеха и ошибки через Result. Когда мы столкнулись с этой проблемой, добавили в макрос objc_to_swift_equivalents() детальный раздел с примерами всех вариантов конвертации — это значительно улучшило ситуацию. Но есть и обратная сторона: когда в одном промпте собрано слишком много правил, фокус LLM размывается. Модель успешно конвертирует код, но упускает детали при финальной проверке. Поэтому мы разделили ответственность: Swift-conversion отвечает только за корректную компиляцию и базовую замену типов, Refactoring концентрируется на улучшении кода и стиле, Review проверяет результат по чек-листу перед отправкой на ревью.

Кроме того, для ускорения процесса ревью мы использовали внутренние MCP-серверы. Для GitHub, GitLab и Bitbucket уже есть готовые решения. Это позволяет LLM получать комментарии ревьюеров и автоматически исправлять большую часть минорных замечаний: форматирование, переименование переменных, недостающие комментарии. Финальная валидация остаётся за разработчиком.

Проверка универсальности подхода

Чтобы проверить, работает ли подход за пределами Яндекс Браузера, мы решили опробовать его на стороннем проекте. Для эксперимента выбрали приложение Wikipedia для iOS — популярный опенсорс-проект с другой архитектурой и системой сборки. Мы мигрировали SPM-пакет WMFComponentsObjC, который отвечает за форматирование wiki-разметки, адаптировали шаблон под их code style и xcodebuild, заполнили конфигурационный файл, опираясь в том числе на инструкции для Copilot, и открыли PR в репозиторий проекта.

Этот эксперимент показал, что подход не завязан на специфику нашего монорепозитория. Чтобы перенести его в другой проект, достаточно заполнить конфигурационный шаблон с учётом локальных правил, системы сборки и code style.

Результаты и метрики

За время работы над проектом:

  • 106 pull requests влито в master;

  • −97 500 строк legacy кода;

  • −2167 файлов.

На графике ниже показана динамика уменьшения Objective-C-кода. Мы аппроксимировали эти данные и сравнили скорости переписывания. При наивном подсчёте получается ускорение в 5,5 раза, но с учётом затраченных человеко-часов более реалистичная оценка — ускорение примерно в 2,5 раза.

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

Если в вашем проекте есть похожий техдолг — большой, монотонный, с понятными правилами преобразования, — попробуйте этот подход. LLM не сделает всё сама, но возьмёт на себя большую часть рутины и освободит команду для более важных задач. Всё необходимое уже готово: промпты, скрипты, шаблон конфигурации. Адаптируйте под свой проект — и делитесь результатами.

Материалы в открытом доступе

Всё, чем мы поделились в статье, опубликовано на GitHub и SourceCraft. Для удобства дублируем списком: 

  • Шаблон конфигурационного файла rules.md (GH, SC)— заполните под свой проект.

  • Скрипт настройки промптов (GH, SC) — генерирует промпты для вашей IDE.

  • Вспомогательные скрипты (GH, SC) — анализ импортов, переименование классов, исправление импортов.

  • Swift-conversion (GH, SC) — основной промпт переписывания.

  • Refactoring (GH, SC) — улучшение кода под Swift best practices.

  • Review (GH, SC) — проверка по чек-листу перед отправкой на ревью.

  • Интеграционные тесты (GH, SC) — промпт для генерации тестов.

Благодарности

Хотим поблагодарить всю команду Яндекс Браузера для iOS — за готовность пробовать новые подходы, за внимательное ревью и за то, что этот эксперимент стал общим делом.