image

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, а настройки, привязанные к пользователю, лежат в проекто-независимой директории.
Пример Rambafile
### 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-шаблон:
InteractorTemplate.h
//
//  ___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 шаблон для такого же файла:
InteractorTemplate.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 — наше сообщество хоть и небольшое, но достаточно активное.

Полезные ссылки:

Комментарии (8)