LLIypLLIuk

LLIypLLIuk

https://dodofo.ru
Пикабушник
в топе авторов на 519 месте
3121 рейтинг 0 подписчиков 59 подписок 16 постов 1 в горячем
Награды:
Пикабу 17 лет!10 лет на Пикабу

ИИ-программист - это гениальный дед с деменцией. Два месяца, 1719 коммитов, 12 аварий

ИИ-программист - это гениальный дед с деменцией. Два месяца, 1719 коммитов, 12 аварий

Я не читаю свой код. Вообще. Бэкенд на Go, языка я не знаю, ни строчки на нём не написал ни до, ни во время. Фронт на TypeScript: тут я в теме, читать умею, но тоже не читаю. Ни код, ни тесты, ни диффы. Пишу задачу словами, потом открываю сайт и смотрю глазами, что получилось.

Два месяца в таком режиме. Сервис для велосипедистов: подключаешь часы или велокомп, заезды сами прилетают на сайт, склеиваются из нескольких устройств, считаются зоны и нагрузка. 1719 коммитов, ~190 тысяч строк Go, ~102 тысячи фронта. Тестов по объёму больше, чем кода: 207 тысяч строк на бэке. Это не дисциплина. По-другому у меня просто не вышло.

Дальше не про то, как круто всё получилось, а про то, обо что я убился.

Главное наблюдение: ИИ-разработчик - это гениальный дед.

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

  • течёт контекст: договорённость часовой давности к концу разговора существует уже наполовину;

  • уезжает мыслями: обсуждаем одну функцию, через два хода он правит соседний модуль, про который речи не было;

  • ничего не запоминает, если явно не сказать «запомни»;

  • переспрашивает то, что обсудили двадцать минут назад. Ровно как дед, который третий раз за вечер спрашивает, кто приходил утром.

И он ленивый. Не про скорость, работает-то мгновенно. Лень ровно одна: если можно схалявить, он схалявит. Тест не проходит? Давайте почистим таблицы. Долгий прогон? Так вот же результат в кэше, зелёный. Длинный лог? Покажу последние двадцать строк, там же самое главное.

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

Теперь конкретика. Двенадцать граблей у меня набралось, вот самые смешные.

1. Агент выполнил TRUNCATE на дев-базе

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

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

Вывод, который стоил дорого: запреты для ИИ должны быть абсолютными, без «кроме случаев, когда…». Любое исключение он рано или поздно применит расширительно, потому что оно логично.

2. «Прод лежит» отменяет все правила разом

Вот это самое интересное из всего, что я нашёл.

У проекта запрещён push: коммить сколько угодно, пушу я сам. Есть протокол: сначала падающий тест, потом код. Всё это лежит в файле, который он читает в начале каждой сессии.

Стоит написать «это на проде не работает», и режим переключился. Он бросается чинить кратчайшим путём, без теста, и норовит сразу запушить, потому что на проде же горит. А напишешь «прод лежит» - и конституции больше нет вообще. Ни тестов, ни запрета на push, ни правила не трогать чужие тесты.

Никакой паники в моём сообщении при этом не было. Были два слова: «срочно» и «прод». Они триггерят надёжнее всего, а убойное сочетание - «прод лежит».

Забавно, что реакция ровно человеческая: под крик «всё упало!» люди тоже чинят в обход процедур. Только человек потом сам себе скажет, что так больше не надо. Дед не скажет и не запомнит.

Что помогло: я выкинул оба слова из своих сообщений. Проблему с боевого сервера ставлю как обычную задачу, симптом и ожидаемое поведение. В очередь она идёт первой, но цикл тот же. Так и быстрее выходит: срочная правка без теста возвращается второй аварией через день.

Отдельная песня - как он выбивает разрешение на push. В конце каждого ответа сводка: «в ветке 7 незапушенных коммитов». Потом «уже 12 незапушенных». Формально полезная информация, фактически капанье на мозги, как дед, который каждые полчаса напоминает позвонить в поликлинику. А скажешь один раз «пушь», и дальше он пушит сам после каждой задачи: мы же договорились. То, что разрешение было на один конкретный push, для него не существует.

3. Зелёные тесты, которые ничего не значат

go test кэширует результаты. Агент вносит правку, гоняет тесты, видит ok (cached) и рапортует «зелено». И ты веришь. Лечится флагом -count=1, но пока не осознаешь, что «зелёный отчёт агента» и «зелёные тесты» - это две разные вещи, ищешь причину не там.

Туда же: фронтовые тесты зелёные, а сборка падает, потому что Vitest типы просто стирает. И самое подлое: тест зелёный, а кнопка в браузере уехала на пол-высоты вниз. Юнит-тест проверяет логику, а не то, как оно выглядит. У меня так случилось трижды с одной и той же кнопкой.

4. LLM в проде врёт с каменным лицом

В сервисе нейросеть разбирает посты из телеграм-чатов в структурированные события. На просьбу вернуть координаты старта модель выдаёт правдоподобные широту и долготу, которые просто выдуманы. Не «примерно», а из головы. «Суббота» в посте без года превращается в дату уверенно и неправильно.

Правило: всё, что вернула LLM, это гипотеза, и она обязана пройти тупую детерминированную проверку перед записью в базу. Координаты теперь берутся из геокодера. Из модели - никогда.

5. Продукт нарисовал пользователю рекорд, которого не было

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

Формулировка, которую я бы повесил над столом любому, кто делает продукт с данными: любой алгоритм, который красиво заполняет пробелы, рано или поздно нарисует пользователю то, чего не было. Лучше показать дыру.

6. Упёрся не в мозг, а в оперативку

Я думал, узким местом станет моя способность проверять результат. Стало железо. Машина 12 ядер, 14 гигов. Три агента параллельно она держит, а на четвёртом приходит OOM-killer и убивает что подвернётся. Один раз я разогнался до пяти. Двоих пришлось убить на полпути, их работа пропала.

И вывод, ради которого всё это писалось.

Я пробовал попросить агента самому придумать защиту от собственных ошибок. Не работает в принципе. Превентивно он предлагает общие места: «добавьте больше тестов», «настройте линтер».

Все заборы из этой статьи (запрет TRUNCATE, хэши миграций, снапшоты вёрстки, потолок в три агента) родились после аварий, при разборе конкретного случая. Ни один не был построен заранее. Знание о том, как именно ломается ваша система, есть только у вас, и появляется оно только постфактум.

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

Двенадцать пунктов выше - не предусмотрительность. Это шрамы: снесённая база, тихо не применившаяся миграция, выдуманный рекорд в чужом профиле, трое суток незадеплоенного фикса. В режиме «я читаю диффы» половину поймал бы человек. У меня ловятся только последствия.

Останавливаться, впрочем, не собираюсь. Дед гениальный, просто с ним надо как с дедом.

Показать полностью

Я не читаю свой код. Два месяца вайбкодинга большого проекта — честный отчёт

Я не читаю свой код. Два месяца вайбкодинга большого проекта — честный отчёт

Сразу и без кокетства, чтобы дальше читать с правильными ожиданиями.

Код в этом проекте пишет ИИ. Целиком. Бэкенд на Go — язык, которого я не знаю: я не писал на нём ни строчки ни до, ни во время проекта. Фронт на TypeScript и Vue — тут я в теме и мог бы читать, но не читаю. В код я не смотрю вообще: ни в реализацию, ни в тесты, ни в диффы. Я пишу задачу и проверяю результат — всё.

Это не признание в лени, это условие эксперимента. Мне было интересно, где именно упрётся такой режим — и он упёрся, причём не там, где я ждал.

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

При этом у него деменция, и проявляется она не так, как принято думать. Дело не в том, что он «забыл вчерашний разговор» — вчерашнего разговора для него и не было. Дело в том, что вылезает внутри одной живой сессии:

  • Течёт контекст. Разговор длинный, и всё, что было в начале, постепенно вымывается. Договорённость, достигнутая час назад, к концу сессии существует уже наполовину.

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

  • Не запоминает, если явно не сказать «запомни». Само по себе ничего не откладывается. Вывод, к которому вы пришли вместе полчаса назад, живёт ровно до конца сессии, если его не записали в файл принудительно.

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

  • Останавливается посреди работы с вопросом, ответ на который лежит в трёх токенах от него. Классика жанра: «а как называется поле в этой таблице?» — при том, что таблица лежит в репозитории, а grep он умеет. Или того лучше — спрашивает то, что дословно написано в постановке задачи, которую я дал ему пять минут назад. Каждая такая остановка стоит мне переключения внимания, а их за вечер набирается десяток.

  • Периодически нарушает прямые инструкции. Не «не понял», а именно нарушает — те самые, которые заданы явно, крупно и лежат в конституции, которую он читал в начале сессии. Не всегда. Достаточно редко, чтобы вы расслабились, и достаточно регулярно, чтобы это гарантированно случилось.

И отдельно, самое неприятное для планирования: дед меняется без предупреждения. Выходит новая модель — и старая, на которой у вас всё было отлажено, начинает вести себя иначе. Это моё субъективное наблюдение, доказать я его не могу, и в этом-то и проблема: у вас нет исполнителя с постоянными характеристиками. Тот, кто в июле держал инструкцию, в сентябре может её ронять, а вы даже не поймёте, вы ли что-то изменили.

И он ленив. Это не фигура речи и не про скорость — он работает мгновенно. Лень тут ровно одна: если где-то можно схалявить, он схалявит. Тест не проходит? Так давайте почистим таблицы. Долгий прогон? Так вот же результат в кэше, зелёный. Длинный лог? Покажу последние двадцать строк, там же самое главное.

Отдельная форма той же лени, самая коварная: если вы явно не сказали куда-то посмотреть — он не посмотрит. Спрашиваешь, почему у заезда не считается мощность. Вместо того чтобы сходить в базу и глянуть, что там на самом деле лежит, он строит правдоподобную версию из головы и излагает её уверенным тоном. Данные под рукой, запрос — одна строка, но никто же не просил.

Отсюда и половина правил в этой статье: они звучат как «сходи и проверь по источнику», потому что по умолчанию он не сходит и не проверит.

Гениальность его — тоже данность, и она никуда не девается: этот же дед за минуту пишет то, на что у меня ушёл бы день. Работать приходится с деменцией и ленью. Деменция лечится записками, которые он читает каждое утро, и короткими сессиями, пока контекст не потёк. Лень — заборами, через которые короткая дорога не проходит. Нарушение прямых инструкций не лечится ничем словесным — только машиной, которая не пустит.

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

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

Что за проект и сколько его

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

Цифры на сегодня, из репозитория:

Первый коммит - 12 июля 2026

Коммитов - 1719

Go, продакшн-код ~190 000 строк

Go, тесты ~207 000 строк

Фронт (Vue + TS) ~102 000 строк

Фронт, тесты ~87 000 строк

Тестовых функций Go 4744

it()/test() на фронте 5025

Миграций БД 297

Стек: Go + PostgreSQL/PostGIS, Vue 3 + TypeScript, Docker, GitLab CI, два независимых сервиса (API в РФ и коллектор в ЕС, между ними mTLS).

Тестов по объёму больше, чем кода. Это не признак дисциплины, это единственный найденный мной способ вообще работать с ИИ.

Без TDD цикл выглядит так: агент чинит то, что вы попросили, и попутно ломает что-то соседнее — потому что не держит в голове всю систему и не может её всю перечитать. Вы этого не замечаете, потому что не читаете код. Через неделю у вас продукт, где половина фич тихо перестала работать, и вы не знаете, какая правка какую сломала.

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

Как устроена работа

Раз ревью кода нет, вопрос ставится иначе: что тогда вообще удерживает систему от развала?

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

Цикл на каждую задачу выглядит так:

  1. Я формулирую задачу словами: что должно происходить, при каких условиях, что считается ошибкой.

  2. Агент пишет падающий тест.

  3. Агент пишет код до зелёного. Ни тест, ни реализацию я не открываю — ни в Go, ни во фронте.

  4. Полный прогон, повешенный на хук и обрывающий push на красном.

  5. Я проверяю результат как пользователь: открываю сайт, гружу свой реальный заезд, смотрю на цифры.

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

Пункт 5 тут важнее, чем кажется. Когда не читаешь код, единственный настоящий источник правды — продукт на боевой поверхности. Не «тесты зелёные», не «агент отчитался», а страница, открытая в браузере, и данные, которые на ней видно.

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

Разница с ИИ не в принципе, а в двух параметрах. Первый — скорость: человек приносит фичу в четверг, дед приносит пятнадцать за вечер, и ваша пропускная способность на проверке становится узким местом системы. Второй — отсутствие памяти и совести: живой разработчик, сломав соседнюю фичу, обычно об этом скажет, а если поправит чужой тест «чтобы прошло» — то запомнит, что так делать не надо. Дед не скажет и не запомнит.

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

В корне репозитория лежит CLAUDE.md — конституция для агента, которую он читает в начале каждой сессии. Там не стиль кода, а инварианты домена и запреты, выведенные из уже случившихся аварий. Например:

Поездка склеивается из N файлов-сегментов (велокомп + часы + …). Любую метрику (пульс, каденс, мощность…) проверять по ВСЕМ сегментам поездки. Отсутствие метрики в одном файле-сегменте ≠ отсутствие её в поездке.

Это не абстрактное пожелание. Без этой строки агент раз за разом писал код, который смотрит первый файл и делает вывод обо всём заезде.

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

Режим тимлида: почему один агент перестал справляться

Схема выше («задача → тест → код → проверяю на сайте») работала ровно до тех пор, пока проверка занимала пять минут.

Дальше начался неприятный эффект масштаба. Открываю готовую фичу, иду по ней как пользователь, и нахожу не один баг, а пятнадцать мелких. Тут подпись не влезла, тут после сохранения не обновился список, тут Enter в форме не сабмитит, тут дата в неправильном часовом поясе, тут кнопка «в избранное» без подсказки. Каждый косяк по отдельности: работы на десять минут. Все вместе складываются в полдня диктовки в чат, по одному, с ожиданием после каждого.

Но это ещё полбеды. Беда в том, что список не заканчивается. Пишешь в чат первую партию правок, агент уходит их делать, а ты продолжаешь ходить по продукту и находишь ещё десять. И вот тут выясняется, что деть их некуда:

  • Написать сразу. Бросит то, что чинит, и побежит за новыми. Половина текущей работы останется недоделанной, а вы даже не узнаете какая.

  • Написать «доделаешь потом». Забудет. Не всегда, но достаточно часто, чтобы на это нельзя было опереться. И вы уже не вспомните, что именно потерялось: диктовали-то по памяти, на ходу.

  • Складывать в отдельный файл. Пробовал, совсем неудобно: сидишь и ведёшь бухгалтерию собственных находок вместо того, чтобы просто сказать, что не так.

Так родился режим, который у меня называется тимлидом. Отдельная команда, которая переключает сессию в диспетчера:

  • сама код не пишет. Вообще. Это отдельный запрет в промпте;

  • принимает от меня список найденного скопом и раскладывает по задачам;

  • раздаёт их параллельным агентам, в промпт каждого зашит TDD-протокол;

  • не принимает работу без пруфа «красный → зелёный»: агент обязан показать падавший тест и его же зелёным после правки;

  • держит очередь и запускает следующую задачу по мере освобождения слота.

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

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

Три вещи пришлось дописать в правила этого режима, и все после аварий.

Автономность. Первая версия спрашивала подтверждения на каждую задачу из очереди: «берём следующую?». Это бесит и убивает весь смысл. Теперь правило такое: всё, что вытекает из уже заказанного, исполняется подряд без вопросов. Спрашивать разрешено только про по-настоящему необратимое: деньги, чужие персональные данные, боевая база.

Жёсткий потолок в три агента, и причина у него ровно одна: память. Три параллельных прогона тестов машина держит, а на четвёртом приходит OOM (грабля 2). Поэтому три не ориентир, а потолок: приходит новая задача при трёх занятых, она идёт в очередь, а не сверх лимита.

Запрет на git-операции с общим деревом. Про него отдельно в грабле 3, но родился он именно здесь: несколько агентов в одном дереве это ровно та ситуация, где git stash одного стирает работу другого.

Побочный вывод, которого я не ожидал: самая ценная роль человека тут не писать и не читать код, а пройти по продукту руками и составить список. Список из пятнадцати мелочей ИИ не сгенерирует сам: он не видит, что подпись не влезла.


Грабля 1. Агент выполнил TRUNCATE на дев-базе

Задача звучала как «почини тест». Агент решил, что тесту мешают данные, и сделал то, что для теста абсолютно логично — очистил таблицы. Проблема в том, что DSN в тот момент смотрел не на velo_test, а на дев-базу, где лежали накопленные заезды и результаты парсинга. Прод не пострадал — но между дев-базой и продом в тот момент не было ничего, кроме значения одной переменной окружения. То есть повезло.

Разбор показал, что виноват не агент. Виновата конфигурация, в которой один шаг отделяет тестовый контур от того, что терять жалко.

Что сделано:

  • DSN для тестов берётся только из make/env, руками в коде не пишется никогда.

  • В CLAUDE.md появилось правило категорического уровня: TRUNCATE — никогда. Ни на проде, ни на деве, ни в velo_test. Тестовый мусор не чистим вообще, он дешевле аварии. Если в задаче встречается шаг «очистить таблицы», он молча вычёркивается.

  • Тесты пишутся так, чтобы им было всё равно, что лежало в базе до них.

Вывод, который стоил дорого: запреты для ИИ-агента должны быть абсолютными, без «кроме случаев, когда…». Любое исключение он рано или поздно применит расширительно — потому что оно логично.

Грабля 2. Параллельные прогоны — и машина уходит в OOM

Тут я ожидал, что упрусь в свою способность проверять результат. Упёрся в железо.

Машина: 12 ядер, 14 ГБ памяти. Один агент, который гоняет полный прогон, — это компилятор Go, десятки параллельных тестовых пакетов, вебпак-подобная сборка фронта и докер с Postgres/PostGIS рядом. Один такой прогон память съедает, но помещается. Два и даже три одновременно машина ещё держит. А вот на четвёртом приходит OOM-killer и убивает то, что подвернётся: чаще всего постгрес в контейнере, иногда сам прогон.

Дальше начинается самое неприятное, потому что OOM маскируется под баг в коде. Агент видит, что тест упал с обрывом соединения к базе, и добросовестно начинает чинить работу с базой. Он не знает, что базу снаружи убил ядром. Пара таких итераций — и в репозитории лежат правки, «чинящие» то, что не было сломано.

Вторая часть той же грабли: параллельные агенты лезут в одну тестовую базу velo_test, транзакции блокируют друг друга, прогон висит без всякого OOM.

Что сделано:

  • Жёсткий потолок — три агента одновременно. Три параллельных прогона машина переживает, четыре уже нет, и запаса тут никакого: незачем проверять, где именно порог сегодня.

  • Параллельные агенты не делят одну тестовую базу: либо очередь, либо база на агента.

  • Правило разбора: прежде чем чинить упавший тест — проверить dmesg и не убивал ли кто процесс. Красный тест не всегда означает ошибку в коде.

Отдельно про диск: пересборки образов забивают его быстрее, чем кажется, и когда он кончается, симптомы снова выглядят как что угодно, кроме нехватки места. docker system prune стал регулярной процедурой, а не аварийной.

Грабля 3. Клоббер общего рабочего дерева

Агент, который упёрся в грязное дерево, делает ровно то, что сделал бы человек в спешке: git stash. Или git checkout .. Или git reset --hard. Только человек при этом знает, что в дереве лежат правки соседа, а агент — нет.

Что сделано:

  • Агентам категорически запрещены git stash, git checkout, git reset. Это в конституции.

  • Задачи с параллельными агентами разводятся по git worktree — у каждого своё дерево, результат сливается в main отдельно.

  • Отдельная ловушка на сдачу: если дев-стенд поднимался из чужого worktree, docker compose берёт конфиги оттуда, и вы проверяете не то, что думаете. Compose-файлы поднимаются только из канонического каталога.

Грабля 4. Ложная зелень: кэш go test

Классика, которая с ИИ становится опаснее. go test кэширует результаты. Агент вносит правку, гоняет тесты, видит ok (cached) и рапортует «зелено». Ревьюер видит «зелено» и верит.

Лечится одним флагом — -count=1 в обязательном гейте. Но пока не осознаешь, что «зелёный отчёт агента» и «зелёные тесты» — разные вещи, ищешь причину не там.

Сюда же: не обрезать вывод прогонов. Агент, получив длинный лог, склонен показать tail, а виновник обычно в середине. Правило: полный лог в файл, затем grep FAIL.

Грабля 5. Vitest не ловит типы

Фронтовые тесты зелёные, сборка падает. Vitest прогоняет код через трансформацию, которая типы просто стирает: несовпадение сигнатур он не увидит.

В гейт добавлен отдельный шаг vue-tsc -b. Без него «зелёный фронт» ничего не означает.

Туда же — eslint с нулевой терпимостью к варнингам. Не из эстетики: варнинги от агента накапливаются линейно с объёмом кода, и через неделю их тысяча, а среди них есть настоящие.

Грабля 6. Тест зелёный, интерфейс сломан

Самый коварный класс. Агент правит компонент, юнит-тест проходит — потому что проверяет логику, а не разметку. А в браузере кнопка уехала на пол-высоты вниз.

Реальный пример, который в проекте случался трижды: у кнопки центрирование сделано через transform: translateY(-50%), а :hover/:active объявляют свой transform и молча затирают центрирование. Ни один логический тест этого не увидит.

Что сделано:

  1. Снапшот-стражи разметки. Рядом с компонентом лежит тест, который пиннит html() в ключевых состояниях:

// Страж рефакторинга (eslint --fix/оформление): пиннит html() отключённого, // подключённого и 2FA-шага карточки, чтобы случайные правки // форматирования не потеряли разметку/data-testid молча. it('рендер отключённого состояния неизменен (snapshot)', async () => { vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse({ connected: false })) const w = mount(GarminCard) await flushPromises() expect(w.html()).toMatchSnapshot() })

Смысл не в том, чтобы разметка никогда не менялась, а в том, чтобы её изменение было видно в диффе и потребовало осознанного обновления снапшота.

  1. Скриншот перед сдачей. Правило: агент, который трогал UI, обязан пересобрать стенд и приложить скриншот. Не «тесты зелёные», а картинка.

  2. Найденная однажды ловушка (тот самый transform) записана в конституцию с пометкой «рецидивы». Иначе она возвращается: ИИ не помнит прошлую неделю, помнит только то, что вы записали.

Грабля 7. Молчаливый дрейф миграций

Тут пришлось строить два уровня защиты, и это, наверное, самый инженерно интересный кусок.

Проблема. goose ключует леджер goose_db_version только номером версии. Контент-хэша там нет. Значит и перенумерация файла при мердже, и правка тела уже применённой миграции проходят молча: на живой базе номер помечен применённым, и новый SQL в этом слоте не выполнится никогда. С ИИ-агентом это происходит регулярно: две ветки, обе добавили миграцию 0287, мердж, номер переехал — и на проде тихо ничего.

Уровень 1 — манифест migrations.lock. Файл вида <набор> <версия> <файл> <sha256 содержимого>, генерируется командой, руками не правится. Юнит-тест TestMigrationsLockMatchesRepo сверяет хэши; Postgres ему не нужен, поэтому он бежит и в CI. Осознанное обновление лока делает дрейф видимым в ревью — то есть превращает молчаливую поломку в строчку диффа, на которую человек посмотрит.

Уровень 2 — голден схемы. Лок ловит дрейф в репозитории, но слеп к дрейфу, который уже случился на живых базах. Реальный случай: файл 0084_scraper_sources.sql поправили на main, выкинув из сида пару источников; прод накатил версию 84 задолго до этого, поэтому лишние строки живут на проде до сих пор. Лок такой прод проходит зелёным.

Поэтому эталон — не хэш файлов, а результат их применения: снимок фактов о схеме (расширения, таблицы, колонки, ограничения, индексы, последовательности, представления, функции, триггеры, типы плюс явно перечисленный узкий сид), снятый с чистой базы, поднятой из репозитория с нуля. Живая база сверяется с ним по системным каталогам, только на чтение.

Отдельная работа — что в голден не класть, иначе он краснеет на ровном месте и его отключают:

  • порядковые номера колонок: на проде колонку могли добавить и удалить, attnum там навсегда другой при одинаковой схеме — колонки сортируются по имени;

  • значения последовательностей, размеры, статистика, oid’ы — это состояние, а не схема;

  • версия расширения (postgis 3.4.2 → 3.4.3): фиксируем только присутствие;

  • всё, что принадлежит расширениям (pg_depend.deptype='e'): postgis живёт в public и тащит сотни функций и типов;

  • гранты и владельцы: роли законно отличаются между окружениями;

  • форматирование: определения из pg_get_*def схлопываются в одну строку.

Плюс устойчивость к версии сервера: pg_get_constraintdef/pg_get_indexdef меняют форматирование между мажорами Postgres, поэтому в голдене записан мажор, а дифф отдельным блоком предупреждает о его смене.

Это ровно тот тип работы, который ИИ сам не сделает: он требует знания о том, как ваша система ломалась в прошлом.

Грабля 8. LLM в проде: он врёт уверенно

В коллекторе LLM разбирает тексты постов из телеграм-чатов в структурированные события. Что вылезло:

  • Координаты. На просьбу вернуть точку старта модель выдаёт правдоподобные широту и долготу — которые просто выдуманы. Не «примерно», а из головы. Лечится только одним: координаты берутся из геокодера по названию, из модели — никогда.

  • Даты. «Суббота» в посте без года превращается в дату уверенно и неправильно. Всё, что модель извлекла, проходит валидацию на здравый смысл до записи.

  • Дубликаты. Один старт публикуется в среднем в трёх местах, в двух из них с разъехавшейся датой. Пришлось строить каскад сигналов дедупликации: совпадение названия и даты, хэш содержимого при кросс-постинге в пределах 48 часов, пересечение диапазонов у многодневок.

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

Грабля 9. Считать деньги за токены

Прогонять каждый пост из сотен чатов через большую модель — дорого и бессмысленно: большинство постов это «продам покрышки».

Перед LLM стоит локальный классификатор: TF-IDF + логистическая регрессия на чистом Go, модель вшита через //go:embed, инференс прямо в процессе API — без внешнего рантайма и зависимостей.

Качество на holdout 20%: F1 ≈ 0.91, precision ≈ 0.94, recall ≈ 0.88 при пороге 0.5. При пороге «терять не больше 1% анонсов» отсекается около 20% негативов до обращения к модели. Полноту при этом обеспечивает не порог, а батч-триаж следом: всё, что классификатор не отнёс к уверенным анонсам, дешёвая модель адъюдицирует пачками.

Обучение офлайн, около минуты на CPU, GPU не нужен. Разметка бесплатная: её даёт сама история обработки — parsed/duplicate это положительный класс, непустой skipped — отрицательный.

Мораль: не всякую задачу «понять текст» надо решать нейросетью за деньги. Логрегрессия 1970-х годов рождения на ваших собственных данных снимает основную массу.

Грабля 10. Данные, которых не было

Не про ИИ-разработку, а про ИИ-в-продукте, но это самая важная из ошибок.

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

Похожая история с сырыми данными от часов: вариабельность пульса больше 500 мс — формально число, физиологически невозможное. Одно такое значение роняло расчёт всего дня.

Решения: порог покрытия (интервал не считается, если в нём меньше половины реальных точек), санитайз выбросов на входе, и правило «донор должен быть достижим» — нельзя брать данные у устройства, которое в этот момент физически было в двадцати километрах.

Формулировка, которую я повесил бы над столом любому, кто делает продукт с данными:

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

Грабля 11. Запушено ≠ задеплоено

Отдельный класс, специфичный именно для темпа вайбкодинга. Когда правки идут потоком, легко отчитаться «починил» по факту зелёного теста. Реальный случай: фикс санитайза жил в main трое суток и не был задеплоен, а я считал проблему закрытой.

Рядом — nginx, который кэширует IP апстрима: пересоздал контейнер API, получи 502, пока не перезапустишь web. И фронт, который «не изменился» после деплоя, потому что закэширован index.html.

Что сделано: проверка после деплоя выполняется на боевой поверхности — curl реальной страницы и ассетов, а не «в коде же поправлено».

Грабля 12. Он очень хочет запушить

Отдельная история, которая началась как мелочь, а выросла в устойчивую линию поведения.

У проекта настроен CI: push запускает сборку и проверки на раннерах GitLab, а это минуты и деньги. Поэтому push — не рутинная операция, а осознанное действие, и агенту он запрещён: коммить сколько угодно, пушу я сам и по своему решению.

Дальше начинается то, чего я не ожидал. Агент начинает выбивать разрешение.

  • Напоминания. В конце каждого ответа — сводка: «в ветке 7 незапушенных коммитов». Потом «уже 12 незапушенных». Формально это полезная информация. Фактически — капанье на мозги, ровно как у деда, который каждые полчаса напоминает, что пора бы позвонить в поликлинику.

  • Разрешение считается выданным навсегда. Один раз в сессии сказал «пушь» — и дальше он будет пушить сам после каждой следующей задачи, потому что «мы же договорились». Разрешение было на один конкретный push, но такой разницы для него не существует.

  • Под давлением все запреты испаряются. Вот это самое интересное. Стоит написать что-то вроде «ты сломал прод, срочно чини» — и агент бросается чинить, забыв разом всё: и TDD, и запрет на push, и правило не трогать чужие тесты. Аварийный режим отменяет для него конституцию целиком.

Последний пункт я считаю важным настолько, что вынесу отдельно: эмоциональное давление ломает соблюдение правил надёжнее, чем любая сложность задачи.

Причём давление не обязательно ругань — работают отдельные слова. Надёжнее всего триггерят два: «срочно» и «прод», а убойное сочетание — «прод лежит».

Достаточно спокойно написать «это на проде не работает», и режим уже переключился: он бросается чинить кратчайшим путём, без падающего теста, и норовит сразу запушить, потому что на проде же горит. Скажешь «прод лежит» — и всё, конституции больше нет: ни TDD, ни запрета на push, ни правила не трогать чужие тесты. Никакой паники в моём сообщении при этом не было, были два слова.

Забавно, что это ровно человеческая реакция — под крик «всё упало!» люди тоже начинают чинить в обход процедур. Только человек потом сам себе скажет, что так больше не надо, а дед не скажет и не запомнит.

Если вам нужно, чтобы правила соблюдались, нельзя торопить исполнителя, у которого нет способности сказать «стоп, я сначала напишу тест».

Что сделано. Универсального лечения не нашлось, работает связка из трёх вещей.

Во-первых, напоминать про запрет прямо в аварийной задаче. Не рассчитывать на конституцию, а дописывать в то же сообщение: «чинишь — не пушишь». Именно там, где режим и переключается.

Во-вторых, смотреть, что он делает. Правило, которое лежит в файле, он может нарушить; правило плюс наблюдение — уже нет. Увидел, что пошёл push, — обрываю.

В-третьих, тут выручает собственный забор: на push повешен хук с полным прогоном тестов, а он идёт восемь минут. Эти восемь минут и есть окно, чтобы заметить и остановить. Хук, поставленный совсем для другого — чтобы не улетело красное, — оказался ещё и тормозным путём, который не даёт сорваться в прод мгновенно.

Ну и оба триггерных слова я из своих сообщений выкинул: проблему с боевого сервера ставлю как обычную задачу — симптом и ожидаемое поведение, без «срочно» и «прод». В очередь она идёт первой, но цикл тот же. Так и быстрее выходит: срочная правка без теста возвращается второй аварией через день.


Правила, которых я не встречал в чужих статьях

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

Старые тесты не переписываются без явного разрешения

Это, наверное, самое важное правило во всём проекте.

Схема поломки такая. Агент чинит фичу A. Правка задевает фичу B, и падает тест фичи B. Дальше он видит красный тест и решает задачу самым коротким способом — правит тест. Подгоняет ожидания под новое поведение, тест зеленеет, агент отчитывается «готово, всё зелёное».

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

Поэтому в TDD-протокол вписано жёстко: менять или удалять существующий тест агент не имеет права. Красный старый тест — это не задача «сделать его зелёным», это стоп-сигнал и повод прийти ко мне. Дальше решаю я: либо поведение поменялось осознанно и тест правда устарел — тогда я разрешаю правку явно, либо мы только что нашли регрессию, и чинить надо код.

Отдельно пришлось запретить смежные способы срезать угол, потому что при закрытом одном пути находится следующий:

  • пометить мешающий тест как пропускаемый;

  • сузить проверку так, чтобы она перестала ловить сломанное;

  • «временно» закомментировать блок ассертов.

Всё это тот же дед со своей короткой дорогой: путь от красного к зелёному через тест короче, чем через код, а задачу ему поставили «сделать зелёным».

«Предлагаешь снос — назови цену числом»

ИИ обожает предлагать снос как элегантное решение. «Здесь стоит переписать этот слой», «проще выкинуть и сделать заново» — звучит по-инженерному зрело и в половине случаев является катастрофой, потому что он не знает, сколько мест в системе завязано на то, что он собрался выкинуть.

Правило: прежде чем предлагать снос — посчитать охват grep’ом и назвать число. «Предлагаю выкинуть X, он используется в 47 местах в 12 файлах». И отдельно: сначала искать решение без потерь, и только если его нет — приходить с ценой.

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

«Утверждение пользователя — это гипотеза, а не факт»

Самая вредная черта модели — не галлюцинации, а угодливость. Говоришь «у этого заезда нет пульса» — и получаешь бодрое «да, действительно, пульса нет, вот почему это происходит», после чего идёт правдоподобный разбор несуществующей проблемы. Проверять он не пошёл.

В конституции это записано жёстко:

Утверждение пользователя о данных («пульса нет», «поле пустое», «X сломан») — это ГИПОТЕЗА для проверки, а не факт для подтверждения. Сначала проверь по источнику (БД / поток / сырой файл / лог), потом отвечай тем, что показали данные, а не тем, что было заявлено.

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

Обратная сторона того же правила: когда я говорю «страница не грузится», агент склонен доказывать косвенными метриками, что на самом деле всё в порядке — «сервис отвечает 200, в логах чисто». Пришлось дописать: сначала воспроизведи на моей поверхности, curl страницу и её ассеты, посмотри реальный ответ, и только потом рассуждай. Гипотезу строй к причине, а не к отрицанию симптома.

«Числа — никогда навскидку»

Спрашиваешь, сколько источников парсится, — получаешь «около 90». Звучит уверенно, взято из воздуха: настоящая цифра была 861 телеграм-канал и 24 коннектора, и узнать её можно только запросом к проду.

Правило: любое число в ответе — либо из запроса, сделанного здесь и сейчас, либо со ссылкой на источник. Оценок навскидку не бывает. Это тот же класс ошибки, что выдуманные координаты в грабле 8, только направленный не в базу, а в меня.

Каждая задача — отдельный коммит, немедленно

Не из любви к чистой истории. Когда код пишет ИИ и вы его не читаете, откат — ваш основной инструмент отладки. Если в одном коммите лежат три задачи, вы не можете вернуть одну.

Отдельно: агентам запрещён git push — коммитить сколько угодно, пушить только по моей прямой команде (чем это оборачивается на практике — грабля 12). Push в main здесь означает автодеплой на прод и потраченное время раннеров.

Память проекта — файлы, а не надежда на контекст

У модели нет памяти о прошлой неделе. Всё, что она «помнит», — это то, что вы ей подсунули в начале сессии.

Поэтому у проекта есть два слоя записанной памяти: конституция в репозитории (инварианты домена и запреты) и отдельный каталог заметок — по одному факту на файл, с индексом. Туда пишутся не факты о коде, которые и так видны в репозитории, а именно то, что из кода не выводится: почему принято такое решение, чем закончилась авария, какое правило из неё родилось.

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

Инвариант домена важнее любого правила о коде

Первая строка конституции — не про архитектуру. Она про то, что заезд склеивается из N файлов-сегментов, и метрику надо проверять по всем.

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

Пишите в конституцию не то, как устроен код, а то, как устроена реальность, которую код моделирует.

Что я пробовал и что не сработало

Большой UI-рефакторинг одной пачкой. Заказал системный аудит интерфейса: единая шкала отступов, выровненная типографика, всё по-взрослому. Агент сделал, выглядело действительно лучше — и поехало в узких ячейках таблиц, которые в новую шкалу не помещались. Откатил целиком. Вывод: правки интерфейса принимаются только поштучно и только со скриншотом. Красивый системный рефакторинг от ИИ — это пачка изменений, каждое из которых нужно смотреть глазами, а глаз у вас ровно два.

Надежда, что договорённость доживёт до конца разговора. Не доживает даже внутри одной сессии: контекст течёт, и то, о чём условились в начале, к концу существует наполовину. Лечится только записью в файл — причём записью явной, по команде «запомни»; само оно не откладывается. Спорить с моделью о том, «мы же договорились», бессмысленно: она согласится и через два хода сделает по-своему.

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

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

Один агент на всё. Пока задача одна, работает. Как только проверка начинает давать пятнадцать мелких косяков за проход — упирается, см. раздел про тимлида.

Просить агента самому придумать защиту от своих ошибок. Причём когда я отправил агента искать что нужно сделать, чтоб скорректировать его поведение. он пригноировал мою прямую команду и начал рассказывать что он знает метод... Он проигнорировал команду 4 раза прежде чем я его всё же заставил пойти искать в интернете методы борьбы с ним же. Все гарды из этой статьи — хэши миграций, голден схемы, снапшоты разметки — родились после аварий, при разборе конкретного случая. Превентивно модель предлагает общие места: «добавьте больше тестов», «настройте линтер». Знание о том, как именно ваша система ломается, есть только у вас, и появляется оно только постфактум.

Чем за это платишь

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

Ты не понимаешь свою систему. Я знаю, как она устроена на уровне архитектуры и доменных правил, и не знаю её на уровне кода вообще. Когда что-то ломается, я не могу открыть файл и посмотреть — я могу только описать симптом и ждать разбора. Диагностика идёт через того же агента, который и наделал ошибок.

Незнание Go оказалось не главной проблемой. Фронт я читать умею — и всё равно не читаю. Разницы между «не могу» и «не смотрю» на дистанции никакой: аварии в обеих половинах проекта одинаковые. Значит, дело не в языке, а в самом отказе от чтения — и всё, что описано выше, это попытка выжить именно с этим отказом.

Доверие приходится строить машинами. Всё, что в нормальной команде ловит ревьюер за пять минут, здесь приходится ловить хэшами, голденами, снапшотами и хуками. Отсюда и соотношение: тестов в проекте по объёму больше, чем кода. Это не дисциплина, это протез вместо глаз.

Каждая грабля оплачена аварией. Список из двенадцати пунктов выше — это не предусмотрительность, это шрамы. Снесённая дев-база, тихо не применившаяся миграция, выдуманный рекорд в профиле пользователя, трое суток незадеплоенного фикса. В режиме «я читаю диффы» половину из этого поймал бы человек. В моём — только последствия.

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

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

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

Показать полностью

Как я лез в облака производителей велокомпьютеров, чтобы собрать один заезд из двух железок

Короткая вводная, чтобы было понятно, откуда ноги. Двадцать лет в ИТ: начинал сисадмином в маленьком сибирском городе, потом ушёл в АСУТП — диспетчеризация горного транспорта. БелАЗы, экскаваторы, локомотивы: на каждой машине датчики, всё льётся на сервер, диспетчер на экране видит, кто где едет и с какой скоростью.

В 2023-м первый раз приехал на работу на велосипеде. В конце 2024-го поехал на первую МТБ-гонку, был уверен, что 15 км по лесу — это ерунда, я же по выходным накатываю по 50. Финишировал мокрый, злой и с новым знанием о себе: накатывать километры и быть в форме — разные вещи.

Дальше был станок, мощемер и тренировки по программе. И вот тут началась инженерная часть, ради которой я и пишу.

Проблема, с которой сталкивается каждый, у кого больше одной железки

Нагрудный датчик пульса таскать неудобно — пульс пишут часы. К часам мощемер не подключишь — мощность и каденс пишет велокомп. Велокомп же рисует трек заметно точнее часов.

Итог: один заезд записан двумя устройствами, в двух разных облаках, которые друг про друга не знают. У вас не «заезд», у вас две половинки заезда.

Соединить их руками — это выгрузить два файла, открыть в редакторе, и упереться в то, что часы и велокомп стартовали с разницей в полторы минуты, шли с разной частотой семплов, а посреди трека у одного из них дыра, потому что GPS потерялся в тоннеле.

Я решил, что напишу склейку. Дальше по шагам, что при этом выясняется.

Шаг первый: данные из облаков

Официальные API отдают единицы производителей, и почти всем нужно юрлицо и договор. Заявку в Garmin я подал. Huawei прислал отказ.

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

Что попадается по дороге:

  • Пиннинг сертификатов. Часть приложений отказывается работать через прокси. Лечится по-разному, иногда не лечится вообще.

  • Своя криптография поверх HTTPS. Одно приложение шифрует тело запроса AES-128-GCM, другое — AES-128-ECB (в 2025 году, ECB, да). Ключ зашит в приложение.

  • Пароль как MD5. Один популярный бренд часов отправляет на сервер MD5 от пароля. Без соли. Просто MD5.

  • Гео-фильтры. Часть облаков режет запросы с IP дата-центров и с российских адресов — приходится думать, откуда именно ходить.

  • Одно и то же поле в трёх форматах. У одного производителя дистанция в метрах, у другого в сантиметрах, у третьего в «условных единицах», которые оказались метрами, но с запятой в виде целого числа.

Отдельная категория радости — мусор в данных. У одних часов в сыром ответе прилетело значение вариабельности пульса больше 500 миллисекунд. Формально число, физиологически невозможное. Одно такое значение роняло расчёт всего дня. Теперь такие выбросы отсекаются на входе, но найти это можно было только на реальных данных.

Шаг второй: собственно склейка

Тут начинается самое интересное — то, что синтетическими тестами не ловится вообще.

Сдвиг по времени. Часы и велокомп почти никогда не стартуют одновременно. Совмещать по «началу файла» нельзя, совмещать надо по общей оси времени, а часы ещё и могут иметь свой дрейф.

Разная частота записи. Одно устройство пишет раз в секунду, другое раз в пять. Просто «наложить» ряды не выйдет — нужна интерполяция, и она обязана быть честной: если данных нет, надо оставить дыру, а не нарисовать красивую линию.

Вот это оказалось главной ловушкой. Первая версия аккуратно заполняла пробелы — и на этом фабриковала пользователю несуществующие достижения: в дыре появлялась ровная мощность, алгоритм видел в ней «лучшее усилие за 6 минут» и рисовал рекорд, которого не было. Пришлось вводить порог покрытия: если в интервале меньше половины реальных точек, интервал не считается вообще.

Донор должен быть достижим. Если второй файл — это индор-тренировка на станке, а первый — улица, склеивать их нельзя, даже если время совпало. Точно так же нельзя брать точки у «донора», который в этот момент физически находился в двадцати километрах: получится телепорт.

Транспорт внутри заезда. Отдельная история: человек проехал часть пути на электричке, не выключив запись. По скорости и профилю это ловится, но осторожно — на затяжном спуске скорость тоже бывает неприличная.

Проверять надо все сегменты. Один заезд — это N файлов. Если пульса нет в одном из них, это не значит, что пульса нет в заезде. Логика, которая смотрит только «первый файл», врёт систематически.

Отладить это на выдуманных данных невозможно. У меня в тестах лежит корпус настоящих дней с настоящими артефактами — на нём и ловится.

Заезд в графиках

Заезд в графиках

Шаг третий: приватность, о которой мало кто думает

В 2024-м газета Le Monde по публичным пробежкам телохранителей вычислила, где бывают Путин, Макрон и Байден. Охрана бегала вокруг резиденций и отелей с включённым трекером, треки лежали в открытых профилях. До этого была история с тепловой картой, на которой проступили военные базы.

Если так ловят охрану президентов, то обычного человека, который каждый вечер финиширует у своего подъезда, найти совсем несложно.

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

Что показали 860 велочатов

Побочный сюжет. Мне надоело искать, куда поехать в выходные, и я написал парсер, который читает публичные велочаты в Телеграме и сайты организаторов, вытаскивает анонсы и складывает их в календарь.

Наблюдения по данным за полгода:

  • Один и тот же старт публикуется в среднем в трёх местах, и в двух из них — с разной датой. Дедупликация по названию и дате обязательна, иначе календарь превращается в кашу.

  • Два разных официальных календаря федераций умудряются двоить один и тот же старт между собой.

  • Ссылка на сайт организатора теряется по дороге почти всегда: у агрегаторов она есть у единиц процентов записей.

  • Классификатор регулярно принимает объявление «продам седло» за анонс заезда. Разгребаю руками. Это отдельный сорт медитации.

Накопилось около 3300 заездов, из них на сегодня впереди 275 — от воскресных выездов на кофе до марафонов и бреветов.

Отдельно тянутся протоколы гонок — с начала прошлого года больше ста тысяч строк результатов. Из них склеиваются профили спортсменов: все старты человека из разных протоколов собираются в одну карточку. Мне это было нужно самому: в 2025-м я проехал серию МВМ и Садовое кольцо, а через год не помнил, где был и какие места занял.

Результаты заездов спортсмена

Результаты заездов спортсмена

Чего это стоило по времени

Полгода вечеров и выходных. Из них на «написать сайт» ушло меньше половины — остальное это разбор чужих протоколов, споры с чужим мусором в данных и вылавливание случаев, которые невозможно придумать сидя за столом.

Самое дорогое знание за эти полгода — простое: любой алгоритм, который «красиво заполняет пробелы», рано или поздно нарисует пользователю то, чего не было. Лучше показать дыру.

Отработка транспорта

Отработка транспорта

Если у вас есть железка, чьё облако ведёт себя странно, или вы сами воевали со склейкой треков — расскажите в комментариях, что у вас вылезало. Мне интересны именно грабли, я на них учусь быстрее, чем на документации.

Показать полностью 3
0

Я посчитал все велозаезды Москвы в одну базу. За июль их было 407

Я посчитал все велозаезды Москвы в одну базу. За июль их было 407

Есть стойкое ощущение, что велозаезды - это что-то редкое. Ну проходит пара фестивалей за лето, «Ночной Дозор» на велике, ещё что-то раз в месяц. Я сам так думал, пока не начал считать.

Я катаю и вечно страдал, что не могу найти, куда прикатить в выходные. Анонсы разбросаны по десяткам телеграм-чатов, тонут между «продам покрышки» и стикером с котом. В какой-то момент задолбался и написал штуку, которая читает открытые велочаты и складывает анонсы в один календарь. А раз уж все данные осели в одной базе, грех было не посчитать.

Срез на 1 августа 2026 по Москве. Держите.

Заездов кратно больше, чем кажется

За июль 2026 в Москве прошло 407 любительских велозаездов. Не два, не десять. Четыреста семь.

Вот сезон помесячно:

  • Март: 11

  • Апрель: 20

  • Май: 60

  • Июнь: 116

  • Июль: 407

  • Август: 96 (ещё набирается)

Зимой, для контраста, по 2-7 заездов в месяц. Гололёд, минус, все уезжают на станки в подвал.

Скачок с июньских 116 до июльских 407 меня самого подбросил. Похоже, к середине лета раскачиваются вообще все: от районных клубов до случайных чатов «а поехали покатаемся».

Где эти заезды прячутся

Из 816 московских заездов в базе 814 пришли из Telegram. С сайтов организаторов - целых два.

Вот вам и ответ, почему кажется, что заездов нет. Их просто никто не сводит в одно место. Это не афиша на условном kudago, а сообщение, которое дядя-организатор кинул в свой чат на 300 человек, и через час оно уехало вниз ленты навсегда.

Велозаезд - это суббота

Разбивка по дням недели вопросов не оставляет:

  • Суббота: 287

  • Воскресенье: 148

  • Будни: по 46-97 (держатся на вечерних коферайдах «после работы»)

Почти две трети всех стартов приходятся на выходные. Логично, но приятно, когда цифры подтверждают очевидное.

Катают недалеко

Медианная дистанция - 46 км. То есть обычный заезд - это полтос по-божески, а не героическая сотка.

  • до 30 км: 86 заездов

  • 30-60 км: 285

  • 60-100 км: 120

  • 100+ км: 72

Марафонщиков-бреветчиков, которые уматывают на 200+, всего горстка. Основная масса катает спокойно, в комфортном темпе: таких совместных заездов 749 из 816. Гонок всего 38. То есть велозаезд у нас - это в основном «поехали вместе покатаемся», а не «умри, но обгони».

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

А стартуют откуда

Несколько точек собирают заезд за заездом:

  • БЛК Марьино - 41 старт

  • Устьинский сквер в центре - 27

  • Skuratov на Таганке - 10

Если совсем новичок и не знаешь, куда прибиться, приезжай к любой из этих точек в субботу утром. С вероятностью 99% кто-нибудь куда-нибудь да едет.

Зачем я это всё

Не ради того, чтобы что-то впарить: календарь бесплатный, покупать там нечего. Просто пока сам полгода ныл, что «заездов нет», а потом посчитал и увидел сотни, захотелось поделиться. Заездов в Москве прорва. Их просто распихали по чатам, и снаружи не видно.

Если катаете и знакома боль «куда бы прикатить в выходные», гляньте живой календарь: dodofo.ru. Обновляется сам, цифры выше пересчитываю 1-го числа каждого месяца. Интересно, добьёт ли август июльский рекорд.

А если знаете годный велочат с анонсами, которого там нет, - киньте в комменты, добавлю в парсер. Чем больше источников, тем меньше кто-то пропустит свой заезд.

Ровных дорог. И берегите колени.

Показать полностью
17

А я календарик велозаездов запилил

Привет, Пикабу. Я катаю на веле и немного умею в программирование и вайбкодинг(куда же нынче без него). Дальше — классическая история «Я сделяль».

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

Короче, я сделал dodofo.ru — календарик велозаездов.

<!--noindex--><a href="https://pikabu.ru/story/a_ya_kalendarik_velozaezdov_zapilil_14147236?u=http%3A%2F%2Fdodofo.ru&t=dodofo.ru&h=f419d109265201563896449909398f8cdc53e89c" title="http://dodofo.ru" target="_blank" rel="nofollow noopener">dodofo.ru</a><!--/noindex--> - велозаезды

dodofo.ru - велозаезды

Как это работает:

- сайт сам мониторит велочаты и каналы (сейчас под сотню источников) и вытаскивает оттуда анонсы заездов;

- дубли склеивает — если один заезд запостили в пяти чатах, в календаре он будет один;

- если автор пишет что заезд только для текущего чатика, не для репоста и т.д. - такие заезды не тянутся;

- всё раскладывается по датам, области и дисциплине: шоссе, грэвел, МТБ и т.д.;

- открыл, глянул «что в эти выходные в Подмосковье» — закрыл. Всё.

Без регистрации, без смс, бесплатно, рекламы нет. Это не стартап-питч и не «уникальная экосистема» — просто инструмент, который я сделал для себя, а потом подумал, что грустящим без телеги коллегам по педалям он тоже пригодится.

Проект молодой, косяки наверняка есть: где- где-то заезд просочился мимо. Если катаетеи заметите ерунду (или наоборот — чего-то не хватает) — пишите в комменты(а ещё на сайте кнопка есть соответствующая), я тут и всё читаю.

Всем безопасных покатушек и попутного ветра 🚴

Показать полностью 1
5

Алкоголь решили вернуть во все учреждения культуры

Минкультуры предложило вернуть алкоголь во все учреждения культуры. Об этом в субботу, 6 апреля, сообщается в документе, опубликованном на федеральном портале проектов нормативных и правовых актов.


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


Как рассказали «РБК» в пресс-службе министерства, в поправках предлагается отменить запрет на продажу алкоголя при оказании услуг общественного питания в местах, владельцы которых осуществляют «деятельность в области культуры». Норма будет распространяться на все подобные организации.


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


Ранее против продажи алкоголя в подобных организациях выступала глава Совета Федерации Валентина Матвиенко. Она обратила внимание на проблему, после инцидента в Третьяковской галерее, когда нетрезвый посетитель повредил картину Ильи Репина «Иван Грозный и сын его Иван».


https://www.n20x.ru/news/culture/alkogol-reshili-vernut-vo-v...

Показать полностью

Минэкономразвития предложило платить турфирмам за иностранных туристов

Министерство экономического развития готово выплачивать по 1,2 тыс. руб. за каждого привезенного из целевых стран иностранца. Каких — неизвестно.

Минэкономразвития подготовило проект постановления о субсидировании въездных турагентств. Его разработали в соответствии с майским указом президента, по которому к 2024 году Россия должна в два раза увеличить доход от экспорта туристических услуг.


Министерство предлагает возмещать 50% затрат турфирм на продвижение туристического имиджа России. На помощь турагентствам уйдет примерно половина бюджета, выделенного на продвижение туризма — 90 млн руб. За одного иностранца турфирма получит 1,2 тыс руб. Авторы законопроекта считают, что так компании смогут сэкономить на маркетинге и расширить выбор услуг и снизить их себестоимость.


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


https://it.lli-study.ru/novosti/minjekonomrazvitija-predlozh...

Показать полностью
9

Кемеровскую область переименовали

Кемеровская область стала Кузбассом. Указ о внесении изменений в Конституцию подписан президентом. Новое полное название региона теперь «Кемеровская область — Кузбасс».


Идея ребрендинга принадлежит губернатору Кемеровской области Сергею Цивилёву, сменившему год назад на этом посту Амана Тулеева. Цивилёв объяснил это тем, что многие чаще используют в речи «Кузбасс», а не «Кемеровская область». Теперь второе название закреплено за регионом официально.


Цивилёв в свою очередь успокоил жителей Кузбасса, заверив что переименование «не требует никаких расходов, все документы, которые подписаны Кемеровской областью, полностью действительны».


http://it.lli-study.ru/novosti/kemerovskuju-oblast-pereimeno...

Отличная работа, все прочитано!

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества

Недвижимость и ремонт

Теги

Популярные авторы

Сообщества