Как я за полдня навайбкодил библиотеку на Go и почему потом всё-таки пришлось вчитываться в код


TL;DR: В этой статье расскажу, как за полдня с помощью Antigravity и подписки Google AI Pro я собрал полноценную Go-библиотеку для работы с конфигурацией: несколько форматов, переменные окружения, каскадная загрузка, валидация, кастомные провайдеры и всё остальное, что обычно начинает появляться в проекте после нескольких недель жизни микросервиса. А ещё поделюсь, почему после первых нескольких часов восторга мне всё-таки пришлось открыть исходники и внимательно посмотреть, что там написал этот замечательный агент.
Пишу статью ради того, чтобы привлечь внимание опытных Go-разработчиков, желающих попробовать библиотеку и дать обратную связь или посодействовать дальнейшему её улучшению/развитию. Милости прошу в репозиторий: https://github.com/Denio1337/goconf.
И да – это моя первая статья на Хабре, так что буду рад конструктивной критике, после прочтения приглашаю всех в комментарии.
Немного о себе
Меня зовут Данил, мне 23 года. Год назад я закончил университет, во время учёбы успел потрогать и фронтенд, и бэкенд, но в итоге окончательно понял, что на бэкенде мне как-то спокойнее.
Особенно мне зашёл Go. В нём есть какое-то приятное ощущение: вот тебе структура, вот функция, вот ошибка – разбирайся. Без ощущения, что язык сейчас внезапно достанет из-за пазухи ещё одну магическую абстракцию. Предсказуемость, строгость, статическая типизация, компилятор вместо интерпретатора, удобная работа с параллельным и асинхронным кодом, автоматическая сборка мусора, солидная производительность - всё это стало факторами, повлиявшими на моё отношение к языку.
Откуда вообще взялась идея
Есть у меня одна привычка: периодически зависать на Хабре и читать статьи обо всём подряд. Если встречаю незнакомую технологию или концепцию – иду дальше: документация, Google, ChatGPT, исходники. Недавно наткнулся на статью Т-Банка о проблемах библиотек конфигурации в Python: зоопарк форматов, неявные преобразования типов, секреты и прочие радости.
Мог бы просто дочитать и пойти дальше, но меня неожиданно охватило желание написать собственную библиотеку. Исначала я решил взглянуть, что мы имеем на данный момент в Go:
Viper – известный тяжеловес, который умеет очень много, но для небольшого сервиса может выглядеть избыточно.
cleanenv и envconfig – лаконичные библиотеки, хорошо заточенные под переменные окружения и
.env.Есть koanf – довольно гибкий вариант, но со своей настройкой и бойлерплейтом.
В общем, всё необходимое уже было. И именно поэтому мне, конечно же, захотелось написать ещё одну библиотеку конфигурации. Потому что иначе какой же из меня программист, если я не написал собственную реализацию велосипеда?

Что я хотел получить
Перед началом я выписал для себя несколько требований:
Отсутствие тонны настроек. В идеальном случае я хочу написать что-то вроде
Load(&cfg)и получить готовую структуру, в которой все поля заполнились соответствующими значениями из файла.Поддержка разных форматов.
.env,JSON,YAML,TOML,INIи переменные окружения.Каскадная загрузка. Например, взять базовый конфиг, поверх него наложить локальный, а поверх всего переменные окружения.
Минимальное количество тегов. Не хотелось вручную размечать каждое поле десятью тегами.
Поддержка внятной валидации.
required,default, проверка неизвестных ключей и возможность написать собственную валидацию.Работа с секретами. Пароль от базы или API-токен не должны случайно улететь в лог только потому, что кто-то сделал
fmt.Println(cfg).
На бумаге всё выглядело довольно безобидно. А дальше я решил проверить, насколько хорошо всё это сможет собрать агент.
Немного боли перед началом
Для начала нужно было вообще запустить Antigravity. Последние несколько лет я разрабатывал преимущественно на Windows, постепенно превращая систему в склад всевозможных setup.exe, портативных бинарников, PowerShell-скриптов и утилит, которые я когда-то скачал «просто попробовать». В какой-то момент мне это надоело, и я переехал на Linux через dual boot. А затем очередное обновление Windows сломало загрузчик. Я его восстановил, но после этого решил, что два полноценных мира на одном SSD – это не то, чем я хочу заниматься по вечерам. Так я пришёл к WSL и Visual Studio Code.
CLI-версию поставить оказалось легко, а вот заставить её нормально работать – уже нет. Официально доступ к сервису из России запрещён, поэтому пришлось разбираться с обходом ограничений. В итоге я наткнулся на великолепный репозиторий с активатором, попробовал его TUI-версию, всё настроил, запустил и получил прекрасный 400 и всякие illegibility check от Google. Возможно, проблема была связана именно с особенностями работы сети в WSL или я где-то что-то недокрутил. В любом случае, Antigravity CLI у меня так и не завёлся.
Пришлось нарушить собственный принцип разделения систем и поставить Antigravity на Windows, и всё внезапно заработало. Но из-за такого сценария работы возникла необходимость сразу объяснить агенту:
Рабочая директория и весь проект находятся в WSL. Команды выполняй там.
К моему удивлению, агент действительно нормально это переварил. Перед командами он начал использовать wsl -e bash -c "...", спокойно создавал файлы, запускал go test, устанавливал зависимости. На этом месте я уже начал подозревать неладное.
И вот тут стало интересно
Первый промпт был максимально приземлённым. Я попросил создать Go-модуль github.com/Denio1337/goconf, начать с .env, но сразу заложить архитектуру, в которую потом можно будет добавлять другие форматы. И вот здесь произошло то, чего я, честно говоря, не ожидал.
Через некоторое время я получил не “простыню кода, которую надо разгребать”, а вполне нормальный Go-проект: отдельные файлы, разделение ответственности, комментарии, примеры использования, README, тесты.
И самое главное – всё это действительно запускалось и работало как надо. Агент самостоятельно прогнал: go test, go vet, go fmt, go doc.
Я понимаю, что первоначальная задача была довольно простой, но результат выглядел не как одноразовый сгенерированный кусок кода (чего я ожидал), а как начало настоящей библиотеки, которую ручками написал человек.

Так появились: .env, INI, TOML, YAML, JSON, переменные окружения, единая структура конфигурации, валидация, примеры, дополнительные механизмы расширения.
Причём несколько раз агент сам предлагал решения, о которых я не подумал. Например, когда я хотел внести ломающие изменения, он не стал просто переписывать старый API. Вместо этого сохранил старое поведение, пометил соответствующие участки как deprecated и добавил новый вариант. К слову, на тот момент репозиторий был локальным, и единственным пользователем библиотеки был я сам, поэтому об обратной совместимости можно было вообще не думать. Но агент этого не знал и заранее продумал на будущее сохранение обратной совмести, что меня здорово удивило.
Где агент начал творить какую-то дичь
Первые часы очень легко создают опасную иллюзию: если агент пишет код, запускает тесты и говорит, что всё прошло – значит, всё нормально. Однако это не так. Он действительно может очень неплохо программировать, но иногда он может решить задачу настолько своеобразным способом, что умираешь со смеху.
Ниже несколько наиболее показательных случаев.
«Я не знаю, почему поле пустое, но сейчас мы это починим»
В репозитории есть каталог с примерами использования библиотеки. Я попросил агента сделать пример, в котором все пять поддерживаемых форматов объединяются в один сценарий. Он это сделал, запустил, всё отформатировал, причесал и уверенно отчитался мне:
Ошибка исправлена, 100% тестов проходят успешно
Но когда я решил запустить код сам, почему-то одно из полей структуры конфигурации оказалось в выводе пустым. Конфигурация выглядела примерно так:
type DatabaseConfig struct {
Host string
Port int
User string
Password string
Database string
}
Database оказался пустым. Я не стал долго разбираться и написал агенту “Всё …, переделывай”. Он быстро выполнил мою просьбу, пример заработал как надо, в выводе все поля непустые, я довольный, делаю коммит, а потом решаю посмотреть, что вообще изменилось с последнего коммита.
Оказалось, что в конфигурационном файле ключ назывался не DATABASE (как поле структуры), а NAME. Вместо того чтобы найти причину несовпадения и исправить пример, агент решил, что библиотеке крайне необходима система синонимов. В декодер добавилось примерно следующее поведение: если структура содержит поле Database, сначала искать DATABASE, а если его нет – искать NAME. Более того, агент решил, что это отличная возможность и для других случаев. В README появилась новая “возможность” библиотеки: автоматическое распознавание часто встречающихся синонимов вроде SERVER, HOST, PORT и других ключей. То есть я получил рабочий пример с исправленной ошибкой, уверенно изложенный отчёт о “100% успешных тестах”, а вместе с этим новую концепцию конфигурационного синонимического словаря, которую никто не просил.

ИИ действительно решил проблему, но не так, как я этого хотел.
«Это же 12-Factor Application!»
Когда я попросил агента привести в порядок README, я упомянул, что библиотека умеет работать с переменными окружения, заданными непосредственно на уровне операционной системы. Для меня это была просто одна из возможностей библиотеки. Для агента – начало большой философской дискуссии. Он решил, что здесь обязательно нужно рассказать мне про 12-Factor Application, а в README появился текст о том, что библиотека полностью соответствует принципам 12-Factor, великолепно стандартизирована и подходит вообще для всего подряд.
Я вырезал этот кусок текста, но при следующем редактировании README агент снова его добавил. И так было несколько раз. В какой-то момент у меня уже было ощущение, что я не редактирую README, а сражаюсь с каким-то особенно упёртым маркетологом. Причём агент, судя по всему, прекрасно помнил, что мы когда-то обсуждали 12-Factor, и считал эту информацию настолько важной, что пытался впихнуть её в текст при каждом удобном случае.
Пришлось отдельно сказать:
Не добавляй больше 12-Factor Application в README.
И только после этого он сдался. И здесь, опять же, всплыло то самое «умное распознавание тегов с помощью поиска синонимов», о котором после каждого редактирования в README появлялось очередное упоминание, удалённое мной ранее.
«Раз уж мы пишем конфиг, давай сразу сделаем из него Kubernetes»
Я попросил агента добавить поддержку ещё одного сценария загрузки конфигурации и заодно привести интерфейсы к более единообразному виду. Вместо того, чтобы просто изменить интерфейс, агент решил, что конфигурация должна стать максимально универсальной. Появились куча абстракций, у всего появились интерфейсы, агент помешался на чистой архитектуре и начал сопровождать всё фабриками, builder’ами и т.д. В коде всё выглядело порядочно, однако я поймал себя на мысли, что код напоминает Java. Это был тот самый момент, когда агент не ошибся технически, он просто слишком хорошо выполнил несуществующую задачу. Благо, просьба упростить решение помогла агенту сориентироваться и избавиться от лишних абстракций.
Этот случай мне понравился даже больше остальных, потому что он хорошо показывает одну особенность работы с агентами: иногда проблема не в том, что модель не умеет программировать, а в том, что она слишком легко соглашается с твоей формулировкой и начинает оптимизировать решение под все возможные будущие сценарии одновременно.

Что в итоге получилось
В итоге у нас получилась полноценная, легковесная и модульная библиотека для работы с конфигурацией на Go – goconf. Она позволяет в одну строчку собирать настройки приложения из множества разнородных источников: файлов .env, JSON, YAML, TOML, INI, и системных переменных окружения с детерминированным каскадным приоритетом (где переменные окружения ОС всегда имеют наивысший вес).
Главный упор сделан на удобство разработчика. Библиотека поддерживает концепцию zero-boilerplate: она автоматически строит иерархические пути ключей для вложенных структур, избавляя от необходимости вручную навешивать теги на каждое поле. При этом под капотом работает строгая система валидации: от проверки типов, обязательных полей и значений по умолчанию до отлова опечаток в конфигах и поддержки кастомной валидации через интерфейс Validator. Для безопасности реализован специальный generic-тип Secret[T], который гарантирует, что пароли, токены и приватные ключи никогда случайно не утекут в логи, консоль или сериализованный JSON.
Проект пока выложен в тестовом режиме под версией v0.1.0, потому что одного моего мнения явно недостаточно для того, чтобы уверенно утверждать о создании библиотеки production ready уровня. Всех заинтересовавшихся приглашаю в репозиторий проекта. С радостью приму ваши Issues и Pull Requests.
Особенно интересно получить обратную связь по архитектуре библиотеки: что можно упростить, где решение переусложнено, какие форматы стоило бы добавить. Ну и если найдёте в коде очередной привет от Antigravity – обязательно расскажите. Возможно, я его ещё не заметил.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.