TL;DR
Мы написали классный кодогенератор для iOS-разработки, обладающий следующими достоинствами:
- Поддержка Swift и Objective-C,
- Использование языка разметки liquid для создания шаблонов,
- Гибкая система управления шаблонами,
- Интеграция с менеджером зависимостей Cocoapods.
Больше подробностей — под катом.
Серьезные решения, касающиеся архитектуры проекта, несут за собой необходимость принятия определенного компромисса. Придерживаемся n-tier структуры — получаем некоторое количество пустых пробросов информации между слоями. Распараллеливаем задачи на несколько потоков — тратим огромное количество времени на решение неочевидных багов. В похожей ситуации, требующей принятия компромисса, столкнулись и мы в Rambler&Co, принимая решение использовать VIPER в качестве стандарта архитектуры всех наших мобильных приложений. Получив отличную модульность и четкое разделение ответственностей компонентов, мы приобрели головную боль в виде сложности и монотонности процесса создания новых модулей.
Среднестатистический iOS разработчик в начале работ над новым экраном просто создает один класс. Тот, кто принял волевое решение перейти на VIPER, в этот момент начинает страдать. В большинстве случаев ему требуется создать пять классов, шесть протоколов и написать пять тест-кейсов. Допустим, что для создания новых модулей наш бедолага наймет профессиональную секретаршу с огромной скоростью печати — но даже в таком случае он вряд ли сможет выйти за рамки 30 секунд на создание и заполнение одного файла. Применим те небольшие знания математики, которыми наделила природа мобильных разработчиков, перемножим эти числа и получим ответ в районе 10 минут. Тот самый среднестатистический разработчик за это же время успеет накидать пару сотен строк UITableViewDataSource, отправить несколько сетевых запросов и покрасить все view’шки в красивый лазурный цвет. Как-то несправедливо по отношению к труду нашего VIPER-гуру.
А ведь тяжелый и нудный ручной труд является не единственной проблемой. Не меньше головной боли приносят множащиеся с каждым модулем опечатки, которые с каждым днем все сильнее и сильнее утягивают проект на илистое дно сорванных сроков.
Одним из способов решения указанных проблем, которым долгое время пользовались и мы — это создание своих собственных шаблонов для Xcode. Не считая того, что такой подход просто не спортивен, для себя мы выделили еще ряд относительно серьезных недостатков.
- Xcode — не самая стабильная IDE, и периодически при его обновлениях шаблоны, плагины и прочий обвес могут успешно слетать.
- Нет удобного механизма добавления новых шаблонов, не говоря уже о получении обновлений.
- Принципиально отсутствует возможность добавления генерируемых файлов в разные таргеты проекта.
- Синтаксис бесконечно неудобный и сложный, особенно для того, кому нужно за десять минут просто накидать шаблон для проекта.
Но, конечно, все понимают, что больше всего расстраивал еще один, фатальный, недостаток — решение написано не нами. Контроля над ним слишком мало, а расширяемость для более специфичных задач стремится к нулю. Поэтому мы и решили написать свой кодогенератор — Генерамбу. Сразу же уточню очень важный тезис — хоть мы и приступили к проекту для того, чтобы упростить процесс создания VIPER модулей, но в результате получили куда более гибкую утилиту, которая может помочь в автоматизации широкого спектра задач по генерации и стандартизации кода.
С момента установки Генерамбы (gem install generamba) до создания своего первого модуля нужно пройти три шага:
- generamba setup запускает процесс настройки Генерамбы для работы с конкретным проектом. В этот момент задаются стандартные пути для файлов, настройки тестов, менеджеров зависимостей и прочей инфраструктуры. В результате выполнения команды мы получаем конфигурационный файл проекта — Rambafile.
- generamba template install запускает процесс установки указанных в Rambafile шаблонов.
- generamba gen HabrahabrModule rviper_controller уже непосредственно создает новый модуль HabrahabrModule, используя шаблон rviper_controller.
Для работы Генерамба использует несколько ресурсов:
- Rambafile — конфигурационный файл, содержащий все, что пользователь указал во время выполнения команды setup, а также ссылки на шаблоны и их каталоги,
- Один или больше шаблонов, которые могут быть указаны при создании нового модуля,
- Пользовательские настройки (к примеру, имя автора).
Первые два пункта смело попадают под Git, а настройки, привязанные к пользователю, лежат в проекто-независимой директории.
### Headers settings
company: Rambler&Co
### Xcode project settings
project_name: GenerambaSandbox
prefix: RDS
xcodeproj_path: GenerambaSandbox.xcodeproj
### Code generation settings section
# The main project target name
project_target: GenerambaSandbox
# The file path for new modules
project_file_path: GenerambaSandbox/Classes/Modules
# The Xcode group path to new modules
project_group_path: GenerambaSandbox/Classes/Modules
### Tests generation settings section
# The tests target name
test_target: GenerambaSandboxTests
# The file path for new tests
test_file_path: GenerambaSandboxTests/Classes/Modules
# The Xcode group path to new tests
test_group_path: GenerambaSandboxTests/Classes/Modules
### Dependencies settings section
podfile_path: Podfile
cartfile_path: Cartfile
### Templates
catalogs:
- 'https://github.com/rambler-ios/generamba-catalog'
- 'https://github.com/igrekde/my-own-catalog'
templates:
- {name: rviper_controller}
- {name: local_template_name, local: 'absolute/file/path'}
- {name: remote_template_name, git: 'https://github.com/igrekde/remote_template'}
Отдельного упоминания достойна работа с шаблонами. В отличии от многих других генераторов, мы не зашиваем шаблоны в саму утилиту — взамен этого мы встроили более гибкую систему, подсмотренную у менеджеров зависимостей (читай, Cocoapods). Новые шаблоны могут быть установлены с использованием одного из следующих путей:
- Локальный шаблон (копируется из указанной папки)
- Удаленный шаблон (клонируется из указанного репозитория)
- Шаблон из каталога (в репозиториях с используемыми каталогами ищется подходящий шаблон по названию).
Спустя несколько месяцев использования Генерамбы на наших проектах, оформился самый часто используемый паттерн — для проекта создается свой каталог шаблонов, в рамках которого хранятся все, даже наименее часто используемые шаблоны. Со временем некоторые из таких решений, заточенных под конкретный проект, после определенных доработок попадают в нашу публичную спеку.
Как я уже упоминал, нас смущала сложность разметки стандартных шаблонов Xcode, и в качестве инструмента борьбы с этой проблемой был выбран шаблонный движок liquid — мало того, что с простым, удобным и понятным синтаксисом, так еще и с кучей дополнительных бонусов, которым можно найти применение не только во фронтенде, но и при кодогенерации.
Для сравнения, Xcode-шаблон:
//
// ___VARIABLE_viperModuleName______FILENAME___
// ___PROJECTNAME___
//
// Created by ___FULLUSERNAME___ on ___DATE___
// Copyright ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved.
//
#import "___VARIABLE_viperModuleName:identifier___Interactor.h"
#import "___VARIABLE_viperModuleName:identifier___InteractorOutput.h"
@implementation ___VARIABLE_viperModuleName:identifier___Interactor
#pragma mark - ___VARIABLE_viperModuleName:identifier___InteractorInput
@end
Liquid шаблон для такого же файла:
//
// {{ module_info.name }}{{ module_info.file_name }}
// {{ module_info.project_name }}
//
// Created by {{ developer.name }} on {{ date }}.
// Copyright {{ year }} {{ developer.company }}. All rights reserved.
//
#import "{{module_info.name}}Interactor.h"
#import "{{module_info.name}}InteractorOutput.h"
@implementation {{module_info.name}}Interactor
#pragma mark - {{module_info.name}}InteractorInput
@end
Мы продолжаем активно развивать Генерамбу. Помимо относительно бытовых задач мы присматриваемся и к другим направлениям:
- добавление GUI, в том числе в виде плагинов для IDE,
- поддержка Android-проектов,
- фантастика, требующая работы с AST-деревом — к примеру, автогенерация тест-кейсов по описанным протоколам или моков для swift-классов.
В качестве заключения хочется добавить, что Генерамба послужила отличным наглядным примером пользы автоматизации вполне тривиальных задач — полученный нами опыт мы перенесем и на другие области деятельности отдела, которым не помешало бы избавиться от ручного труда.
Решили начать использовать Генерамбу? Задавайте свои вопросы и пишите о найденных проблемах в issues — наше сообщество хоть и небольшое, но достаточно активное.
Полезные ссылки:
- liftoff, для генерации Xcode-проектов,
- Книга VIPER,
- Документация Генерамбы,
- Выступление на Rambler.iOS V про Генерамбу: слайды, видео
Автор: Rambler&Co