12 правил code-review с AI: что мы разрешаем команде 42 РМ, а что нет
Есть клиент — продуктовая компания, где работают 42 человека. Их команда разработки из 12 специалистов уже 9 месяцев активно пользуется AI-помощниками: Claude Code, GitHub Copilot и Cursor. За этот период мы накопили данные по PR-ревью: 412 пулл-реквестов, где AI так или иначе поучаствовал, 38 живых багов поймано ещё на стейдже и 7 инцидентов зафиксировано на проде. Мы выработали 12 чётких правил, которые закреплены в CODEOWNERS-файле и pre-commit hooks в их репозиториях. Дальше — эти правила, примеры реальных ошибок, которые они помогают обнаружить, и наш стенд, где мы прогоняем весь AI-код перед мерджем.
Контекст: 9 месяцев работы и что мы реально собрали
Осенью прошлого года ко мне обратился клиент с понятной проблемой: «Помогите нам наладить процесс разработки, наши программисты слишком много времени тратят на рутинные, простые задачи». Они уже полгода пробовали GitHub Copilot, но без всяких правил, и это привело к двум неприятным инцидентам на проде. Один раз произошла SQL-инъекция прямо в endpoint поиска контрагентов, а в другом случае ошибка в обработчике платежей просто молча проглатывалась. После таких случаев мы собрались и я предложил не ставить крест на AI, а наоборот, ввести структурированный подход — не запрещать его, а чётко обозначить границы использования.
За 9 месяцев мои ребята сделали 412 PR-ов, и в каждом из них как минимум 30% строк кода было написано с помощью AI-помощника. Мы это легко отследили по комментариям в коде и, конечно, по логам Cursor или Claude. Изучив эту внушительную выборку, я смог выделить три самых частых типа ошибок, которые постоянно повторялись. Именно на их основе я и составил наши правила.
Цифры по типам ошибок
Тип 1 — «галлюцинации API»: AI вызывает несуществующий метод библиотеки или возвращает функцию, которой нет в установленной версии. У нас 47 случаев за 9 месяцев, из них 31 поймал линтер ещё до коммита, 14 — pre-commit hook, 2 — code review. Тип 2 — «небезопасный SQL и shell»: конкатенация строк вместо параметров, чтение из субшелл-команды без экранирования. 23 случая, из них 17 поймал semgrep, 6 — ревьюер. Тип 3 — «лень в обработке ошибок»: голый except Exception без логирования, return None вместо raise. 38 случаев, ловятся только людьми и линтерами с кастомными правилами.
Правила 1-4: что AI имеет право писать сам
Все эти правила я разделил на четыре блока, в каждом из которых по три пункта. Первый блок посвящён так называемым «доверенным областям». Здесь AI может работать практически самостоятельно, ему нужен лишь минимальный контроль. Сюда относятся типовые CRUD-операции, написание юнит-тестов по уже готовым шаблонам и, конечно, форматирование или миграция кода.
Правило 1: «AI пишет CRUD по существующей модели». Например, есть SQLAlchemy-модель Contract и нужны стандартные GET/POST/PUT/DELETE-эндпоинты — AI справится за считанные минуты, и это безопасно: модель уже валидируется, схема пермишенов наследуется, тесты создаются по отработанному шаблону. Правило 2: «AI пишет юнит-тесты только когда задан pytest-шаблон от человека» — важно, чтобы тесты AI чётко соответствовали контракту, зафиксированному вручную, иначе AI начинает «улучшать» поведение кода под свои же тесты.
Правило 3 и 4 — где AI выручает на нудятине
Ещё одно важное — Правило 3: «AI делает массовые рефакторинги по чёткому промпту». Если нужно, к примеру, переименовать поле user_id в owner_id по всему проекту, это просто идеальная работа для AI. Это невероятно занудно для человека, зато требует точности, и при этом результат потом очень легко проверить через git diff. Могу сказать, что за полгода мы таким образом провели 19 переименований и 4 миграции между фреймворками — взять хотя бы перевод старого django-rest на FastAPI для нашего нового сервиса. И Правило 4: «AI пишет docstrings и комментарии к существующему коду». В этом плане AI просто молодец: он внимательно изучает реализацию и очень хорошо описывает её простым, человеческим языком, почти не допуская ошибок.
Правила 5-8: что AI пишет только под надзором
Переходим ко второму блоку — это наша «серая зона». Здесь AI, конечно, может генерировать код, но только в режиме так называемого «pair programming». Это значит, что разработчик должен сидеть рядом, внимательно проверять каждый шаг и ни в коем случае не оставлять AI без присмотра дольше, чем на 15-20 минут. Что сюда относится? Вся бизнес-логика, включая правила расчётов, обработчики платежей и интеграции с внешними API, а также любая работа с базой данных: SQL-запросы, миграции и оптимизации.
Правило 5 звучит так: «Бизнес-логика, связанная с деньгами, — только pair programming». У клиента на проде есть три важных endpoint'а, которые напрямую отвечают за тарифы и платежи. Да, AI может предлагать варианты кода, но помните: коммит делает только реальный разработчик. И что очень важно, в commit-message мы обязательно ставим специальный тег [pair-with-claude]. Благодаря этому в Git Blame у нас остаётся абсолютно чёткая пометка, какие коммиты требуют особого, пристального внимания во время ревью.
Правило 6 — SQL и работа с данными
Согласно Правилу 6, «любой SQL-запрос, который занимает больше 50 строк, проверяется вручную с помощью EXPLAIN». Знаете, AI с удовольствием генерирует JOIN-ы, которые на первый взгляд выглядят совершенно корректно. Но на деле они могут превратиться в какой-нибудь Hash Join на 200 миллионов строк, который будет сравниваться с небольшим справочником, и это просто катастрофа для производительности. Помню, в феврале был такой случай: AI написал отчёт по контрагентам. На тестовых 1 000 записях он отрабатывал всего за 0,3 секунды, ну просто моментально! А вот на проде, когда записей было уже 280 000, он вдруг начал работать 47 секунд и ещё подвисал на блокировке. В итоге ревьюер сделал EXPLAIN ANALYZE, быстро нашёл недостающий индекс, и мы всё починили буквально за 20 минут.
# pre-commit hook — semgrep + кастомные правила
# .pre-commit-config.yaml
repos:
- repo: https://github.com/returntocorp/semgrep
rev: v1.96.0
hooks:
- id: semgrep
args: ['--config=.semgrep/ai-rules.yaml',
'--error', '--quiet', '--strict']
files: \.(py|sql|js|ts)$
# .semgrep/ai-rules.yaml — наши кастомные правила
rules:
- id: no-fstring-sql
pattern: $DB.execute(f"...{$X}...")
message: "Запрещён f-string в SQL — параметризуй через $1, %s или :name"
languages: [python]
severity: ERROR
- id: no-bare-except
pattern: |
try:
...
except:
...
message: "Запрещён голый except — лови конкретный класс ошибки"
languages: [python]
severity: ERROR
- id: no-eval-exec
pattern-either:
- pattern: eval(...)
- pattern: exec(...)
message: "Запрещён eval/exec в продакшн-коде"
languages: [python]
severity: ERROR
Правило 7: «Миграции базы пишет человек, AI только помогает с шаблоном». В alembic-миграциях у нас был случай, когда AI предложил DROP COLUMN на полном merge-сценарии, и если бы это попало на прод, потеря данных была бы необратимой. С тех пор любая alembic-миграция в этом репозитории помечается @migration-by-human, и без этого CI-бот её блокирует. Правило 8: «Интеграции с внешними API — AI пишет skeleton, но обработчик ошибок и таймауты добавляет человек». Здесь главная боль — AI обожает писать requests.get(url).json() без обработки 502, 504 и таймаутов.
Правила 9-12: что AI трогать категорически нельзя
Третий блок — это наша красная черта, настоящие запреты. Здесь у меня очень жёсткая позиция: к AI я вообще не разрешаю подходить, сам сажусь и внимательно читаю код. В эти зоны попадают такие критически важные вещи, как аутентификация и пермишены, обработка платежей (я имею в виду не бизнес-правила, а именно сами интеграции с банками и эквайрингом), любые операции с персональными данными клиентов, регулируемые 152-ФЗ, и, конечно, работа с production-инцидентами.
Правило 9: «Аутентификация и сессии — никакого AI». В прошлом году было больше 12 публичных инцидентов в индустрии, когда код, сгенерированный AI, для проверки токенов пропускал просроченные сессии или принимал пустой Authorization-header за валидный — AI-помощники чересчур стремятся «упростить» проверки, а это чревато. Правило 10: «Загрузка и обработка файлов от пользователей — только человек, обязательно с песочницей». AI любит выдавать что-то вроде pickle.load(uploaded_file), не задумываясь, что это прямой путь к RCE-вектору.
Правила 11 и 12 — про инциденты и легаси
Правило 11 гласит: «На инциденте production не зовём AI». Помню, как у клиента в марте 'упал' endpoint оплаты. Дежурный разработчик в панике попытался найти решение через Claude. Claude выдал целых три гипотезы, и каждая, надо признать, звучала очень убедительно. Но, увы, ни одна из них не оказалась верной! Настоящая причина крылась в совершенно неожиданной миграции схемы у нашего платёжного провайдера. В итоге мы потеряли целых 40 минут, гоняясь за ложными гипотезами AI, пока я, наконец, не написал: «Закрой Claude и просто читай stack trace». И последнее в этом блоке, Правило 12: «Рефакторинг легаси-кода старше 18 месяцев — только с написания тестов». На старом коде AI очень любит всё «упрощать», из-за чего легко теряются те самые пограничные случаи, которые прошлый разработчик мог добавить когда-то после какой-нибудь серьёзной аварии.
Наш чек-лист на PR-ревью: 11 пунктов
Из всех этих правил родился очень удобный чек-лист. У клиента он сейчас используется как PULL_REQUEST_TEMPLATE.md в каждом репозитории и, что важно, автоматически проверяется нашим CI-ботом ещё до того, как ревью вообще открывается. В нём 11 пунктов, и каждый разработчик должен отметить его либо [x], либо [ ]. Если хоть один пункт остался неотмеченным, CI просто не даст открыть PR. Всё строго!
# PULL_REQUEST_TEMPLATE.md в каждом репозитории
## Контекст
- Задача в Jira: [PROJ-XXXX](https://jira.example.com/...)
- Использовался ли AI-помощник: [ ] нет [ ] частично [ ] >50% кода
- Если AI — какой: [ ] Claude Code [ ] Cursor [ ] Copilot
## Чек-лист перед ревью
- [ ] semgrep + bandit прошли без ошибок
- [ ] нет dynamic SQL, eval, exec, pickle.load
- [ ] нет голых except: Exception
- [ ] нет N+1 запросов в ORM (проверь django-debug-toolbar)
- [ ] миграции идемпотентны и обратимы
- [ ] логирование на ошибках (logger.exception на except)
- [ ] секреты не в коде, только из settings/env
- [ ] юнит-тесты написаны от руки (не AI), покрытие >70%
- [ ] прогнан ai-sandbox: docker compose up, тесты PASS
- [ ] нет дублирования логики с существующими модулями
- [ ] proof of human review: я прочитал каждую строку
Последний пункт — про «proof of human review» — у нас живёт как договорённость, а не как технический контроль. Невозможно автоматически проверить, что разработчик прочитал каждую строку. Но в команде клиента живёт правило: если на ревью находится ошибка, которая «выпала из AI», и автор PR не пометил pair-with-claude, — это считается личной ошибкой автора, а не общей. За полгода это сильно дисциплинировало ребят.
Стенд для тестирования AI-кода: изолированный Docker
Помимо pre-commit hooks и нашего строгого чек-листа, у нас на сервере клиента есть ещё один классный инструмент — отдельный стенд, который мы назвали ai-sandbox. Это по сути полная копия нашего prod-стека, развёрнутая в Docker Compose. Внутри всё как на боевом сервере: Postgres 16, Redis 7, Python 3.12, Node 20, RabbitMQ 3.13 и MinIO для S3-эмуляции. Главное, что данные внутри полностью обезличены. Мы один раз запустили специальный скрипт, который заменил все email, телефоны и имена на синтетические, сгенерированные через библиотеку Faker. А чтобы финансовые суммы выглядели реалистично, мы их умножили на случайный коэффициент от 0,2 до 5.
Вот как это работает: когда разработчик создаёт новую фичу с помощью AI, он обязан перед отправкой PR на ревью прогнать все тесты на ai-sandbox локально. Это делается простой командой docker compose up + pytest. Затем наш CI перепрогоняет то же самое уже в чистом контейнере. На типовой PR уходит где-то 8-15 минут, но за полгода такой подход в 11 случаях помог нам поймать баги, которые при обычной разработке вылезли бы только на стейдже, а это гораздо дольше и дороже. Особенно ценно это для поиска ошибок в подписках и периодических задачах. На ai-sandbox можно, так сказать, «прокрутить» 7 дней всего за 3 секунды, просто подменяя time.time().
Затраты на содержание стенда
Сам стенд ai-sandbox размещён на отдельной VM у клиента. Он довольно мощный: 16 vCPU, 32 ГБ RAM и NVMe 500 ГБ. Это обходится примерно в 8 000 ₽ в месяц на хостинге Selectel. Плюс к этому, я трачу около 4 часов своего времени ежемесячно на его поддержку и обновления — это и новые версии Postgres, и регулярное (раз в квартал) обновление обезличенных данных. По нашей внутренней статистике, один единственный пойманный AI-баг экономит команде от 3 до 8 часов работы разработчика, плюс ещё 1-2 часа QA-инженера. В итоге, за 6 месяцев этот стенд окупил себя просто невероятно — в 7-9 раз!
Результат за 9 месяцев — конкретные цифры
Что было до нас? У клиента еженедельно вылезало 4-5 багов прямо на прод! После того, как мы внедрили все эти правила, количество сократилось до 1-2 в неделю. Да, есть и небольшие минусы: время на code-review увеличилось с 22 минут до 28-31 минуты на один PR. Конечно, это дополнительное время, но поверьте, оно полностью окупается. Зато доля отвергнутых PR заметно упала — с 18% до 6%! Это говорит о том, что разработчики стали учитывать наши правила ещё на стадии написания кода. И самое приятное: скорость доставки новых фич, по замерам Jira, выросла на целых 31% за 6 месяцев! А всё потому, что AI взял на себя всю эту скучную, рутинную работу.
Но что для меня, пожалуй, важнее всего — это то, как изменилось отношение к AI внутри команды клиента. Раньше люди думали: «AI творит магию, а мы просто коммитим». Теперь же установка другая: «AI — это просто инструмент, такой же, как Resharper или git: он помогает, но проверять его всё равно нужно». Один из наших senior-разработчиков на встрече в апреле мне так и сказал: «Я наконец-то перестал бояться, что AI меня заменит. Наоборот, он теперь экономит мне полтора часа в день на рутине, и я могу спокойно заняться архитектурой».
FAQ: что чаще всего спрашивают клиенты
В чём AI-помощники реально ошибаются на корпоративном коде?
По нашим данным за 9 месяцев работы с командой из 12 разработчиков, три типа ошибок ответственны за 78% всех инцидентов. Первый — «выдуманные API»: Claude или Copilot пишут вызов метода, которого не существует в библиотеке, код не компилируется, а в Python падает в рантайме. Второй — небезопасные SQL-запросы, конкатенация строк вместо параметризованных: мы ловили это 14 раз на PR-review. Третий — лень в обработке ошибок: AI генерирует try-except Exception без логирования, и боевая ошибка молча проглатывается.
Можно ли вообще доверять AI-помощникам безопасно?
Доверять — нет, использовать с правилами — да. У нас есть три уровня контроля. Первый — список запретов в репозитории клиента (никаких dynamic SQL, никаких eval/exec, никаких чтений секретов из окружения мимо секрет-менеджера). Второй — обязательный набор pre-commit hooks с semgrep и bandit, которые ловят 40-60% типовых проблем ещё до review. Третий — человек-ревьюер на каждый PR, и в AI-проекте это правило ужесточается: ни одна строка от AI не попадает в main без живого человека на ревью.
Сколько у вас занимает code-review PR с AI-кодом против PR от человека?
Да, по нашим замерам у клиента ревью занимает на 15-25% дольше, но это совсем не приговор! В среднем, живые разработчики команды тратят 22 минуты на ревью PR, содержащего около 200 строк изменений. А вот на PR-ах с AI-кодом — 28-31 минуту. Причина тут не в том, что AI пишет хуже. Дело в том, что я просто не могу полагаться на интуицию ревьюера, который мог бы сказать: «Да этот разработчик такого точно бы не написал». С AI-кодом каждый, даже самый необычный, паттерн приходится проверять вручную. Но есть и отличная новость — это окупается: число багов на проде упало с 4-5 в неделю до 1-2.
Чем у вас разработчики пишут — Claude, Copilot или Cursor?
У клиента в команде 12 разработчиков, и после полугодового пилота они разбились на три группы. 5 человек сидят на Claude Code (Sonnet 4.5 → Opus 4.6 → 4.7) — это те, кто пишет на Python и Go большие куски логики и любит разговорный стиль. 4 человека выбрали Cursor с переключением между моделями — они работают с фронтом на React/TypeScript и любят быстрые подсказки прямо в редакторе. А 3 разработчика на 1С остались с GitHub Copilot — он хорошо натренирован на открытом коде, и для типовых паттернов 1С его подсказок хватает.
Как у вас выглядит изолированный стенд для тестирования AI-кода?
На сервере клиента мы развернули отдельную Docker-среду, которую назвали «ai-sandbox». Это по сути точная копия нашего prod-стека: там и Postgres 16, и Redis 7, и Python 3.12, и Node 20. Важный момент — данные в ней полностью обезличены; мы прошлись скриптом-санитайзером и заменили все email, телефоны и имена на синтетические, сгенерированные библиотекой Faker. Теперь, когда разработчик создаёт фичу с помощью AI, он обязан перед отправкой PR на ревью прогнать все тесты на ai-sandbox и приложить логи. Это занимает где-то 8-15 минут, но за полгода мы благодаря этому поймали 11 багов, которые при обычном подходе вылезли бы только на стейдже.
Итог
Так что, если говорить об AI-помощнике в корпоративном проекте, то это не вопрос «разрешать или нет». Главный вопрос — «как правильно описать границы его использования». Наши 12 правил, подробный чек-лист для PR и выделенный стенд ai-sandbox принесли клиенту потрясающие результаты: минус 70% багов на проде и плюс 31% к скорости доставки фич. А стоит это удовольствие всего 8 000 ₽ за хостинг в месяц и 4 часа моего времени. Зато окупается всё это за каких-то 4-6 недель за счёт колоссальной экономии на инцидентах.
Похожая задача в вашей компании?
Расскажите, что у вас сейчас — пришлю план работ и оценку в течение рабочего дня.
Написать в Telegram или +7 903 729-62-41
Семёнов Е.С., руководитель ITfresh
