Сладость кодогенерации

За последние пару месяцев у меня было много интересных событий - новая работа, всероссийский хакатон, подготовка к конференциям и т.п. И мне довелось активно поиспользовать кодогенерацию в нескольких разных контекстах. Если вкратце - мне понравилось.

Что за кодогенерация

Во многих современных языках кодогенерация достаточно распространена. Чаще всего это применяется чтобы сократить часть издержек рантайма за счет генерации возможной рантаймовой динамики в плоский код. Например, это может быть парсинг JSON-файла известной структуры - вместо того, чтобы тратить время на динамическое чтение структуры, а потом маппинг в определенный тип, можно сразу сгенерировать статичный код под него.

Другой распространенный кейс - это генерация кода по всевозможным манифестам и документациям, например, генерация сервера или клиента по swagger-файлу. Вот об этом мы и поговорим.

Экономия времени

Одной из важных черт кодогенерации является значительная экономия времени. То, что человек будет писать руками час, полдня, а то и несколько дней, может быть сделано вызовом одной команды (конечно, предварительно нужно подготовить какую-то метаинформацию, по которой будет генерироваться код, но это несравненно быстрее, чем писать все вручную, особенно в таких многословных языках, как Go). Ну и конечно всегда приятно генерировать код для тестов, нежели писать его вручную. Во многих языках есть библиотеки, позволяющие значительно упросить написание тестов, генерируя моки и служебный код.

Автоматизация

Экономия времени на написании кода полезна, однако куда более значительно то, что этот процесс можно автоматизировать. Редко когда проект навсегда остается в том же состоянии, как его реализовал разработчик, навсегда. Обычно код живет и эволюционирует, и постоянно претерпевает изменения.

В этом случае автоматизированная генерация будет отличным подспорьем разработчику. Например, при изменении API достаточно отредактировать соответствующий файл спецификации (например, swagger или protofile) и перегенерировать соответствующий код. Соответствующие команды удобно добавить куда-нибудь в Makefile или Ansible, чтобы делать все еще быстрее и проще.

На примере последних двух хакатонов я почувствовал, какое значительное время экономит генерация кода. В первый раз я потратил непростительно большое количество времени на простую реализацию API сервиса - чуть ли не столько же, чем на реализацию логики приложения. При этом немалое время ушло на актуализацию этого кода в свете постоянных изменений и доработок.

Использование же кодогенератора позволило мне вообще не делать ничего этого. Я просто описал API и генерировал к нему серверную часть, а потом подключал сгенерированный код как библиотеку в проекте. При изменении API было достаточно запустить Makefile, и за секунды получить его валидную реализацию.

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

Избавление от ошибок

Еще одним несомненным плюсом является заведомое отсутствие ошибок в сгенерированном коде. Машина всегда делает свое дело одинаково, и если библиотека генерации существует уже давно и имеет большое сообщество пользователей, то большинство ошибок в генераторе уже давно исправлены, а стабильность не вызывает сомнений. Конечно, бывают и ошибки в генерации, и анализировать их достаточно тяжело. Однако это ничто по сравнению с тем временем, которое можно сэкономить на написании тестов и ручных прогонах аналогичного самописного кода.

Упрощение коммуникации в команде

Ну и самое главное - кодогенерация позволяет значительно ускорить и упростить командную работу, когда дело касается каких-то общих ресурсов. Например, по одному swagger-файлу можно генерировать сразу и серверную часть, и клиентскую. Изменения в одном месте сразу актуализируют код во всех использующих его местах.

Это также избавляет от возможных рассинхронизацией в командной работе - файл с описанием сущности (например, API) является единственным источником правды в проекте, а значит не может возникнуть его разночтений. А автоматическая генерация соответствующего кода гарантирует то, что описанные в файле сущности всегда будут одинаково реализованы в коде.

Унификация

И одинаковая реализация тоже немало важна. Например, разработчик, использующий swagger для реализации сетевого взаимодействия может не знать в идеале особенностей OpenAPI или просто не заметить или забыть что-то. В итоге коммуницирующие сервисы будут отправлять и ожидать друг от друга разное, и обмен данными не удастся (а то и хуже, данные перестанут быть консистентными). В случае генерации соответствующего кода можно быть уверенным, что разночтений не возникнет. Конечно, разные реализации в теории могут по-разному интерпретировать какие-то граничные случаи. Но это довольно редко, плюс, всегда можно явно обнаружить и поправить такую ошибку, и она точно не возникнет вновь.

Итого

В целом, хоть в кодогенерации ничего такого и нет, для меня активное использование ее в работе и отдыхе принесло значительное удовольствие.

Мало того, что всегда приятно автоматизировать рутину, так еще можно ускорить командную коммуникацию и сократить время на написание тестов. Красота!

comments