Субпрайм-кризис кода. Долгосрочная цена ИИ-ассистентов

- -
- 100%
- +
Дашборд — это зеркало. Если вы смотрите в зеркало и видите красивого человека, а через год у вас инфаркт — зеркало не виновато. Виноват тот, кто не пошёл к врачу.
5.2. Почему DORA работает, но недостаточен
DevOps-инженер настраивает дашборд. Четыре метрики: lead time, deployment frequency, MTTR, change failure rate. Всё по учебнику. Через месяц выясняется: lead time сократился, deployment frequency выросла, MTTR — стабилен. Но команда выгорела, инциденты повторяются, а новый разработчик не может понять код.
DORA работает. Но DORA измеряет доставку, а не владение. Она говорит, как быстро вы доставляете изменения и как быстро восстанавливаетесь после сбоев. Она не говорит, сколько стоит поддержка, сколько кода никто не понимает, сколько модулей без owner-а.
Метрика Что показывает Чего не видит
Lead time Время от коммита до прода Стоимость владения
Deployment frequency Частота релизов Качество кода
MTTR Время восстановления Причины инцидентов
Change failure rate Доля сбоев Скрытый долг
DORA хороша. Но она не создана для эпохи ИИ. Она создана для эпохи, когда код писал человек, и у кода был автор. Сейчас автора нет. DORA этого не замечает.
Если у вас DORA зелёная и вы думаете, что всё хорошо, — вы смотрите на rating agencies. Они ставят AAA. Через год будет reset.
5.3. AI-specific метрики: то, что DORA не видит
Тимлид добавил пять новых метрик в дашборд. На следующее утро финансовый директор спросил: «Что такое debt interest?» Тимлид объяснил. Финансовый директор кивнул и сказал: «Значит, мы платим проценты по кредиту, который не брали». Тимлид кивнул. Именно так.
Вот эти пять метрик — каждая через сцену.
Review latency.
Пятница, 18:40. Ревьюер открывает четырнадцатый PR за день. 600 строк. Он листает. Функции выглядят знакомо. Тесты зелёные. Он пишет «LGTM» и закрывает ноутбук. Сколько минут он потратил? Четыре. Это review latency. Если меньше тридцати — вы не ревьюите. Вы ставите печать. Через месяц баги уходят в прод. Команда удивляется: «Как мы это пропустили?» Пропустили, потому что не читали.
Rework rate.
Понедельник, 10:00. Разработчик открывает код, который сам сгенерировал неделю назад. Переписывает. Вторник — переписывает снова. Среда — снова. К пятнице он переписал 40% того, что сделал. Это rework rate. Треть того, что вы генерируете, выбрасывается. Вы платите за генерацию, за review, за отладку, за rewrite. Четыре раза за одну строку.
Orphan code ratio.
Тимлид смотрит на список модулей. Пять модулей без owner-а. Авторы ушли. Модули работают. Пока. Если что-то сломается — чинить некому. Пять из двадцати пяти. Двадцать процентов. Это orphan code ratio. Если критичные модули знает один человек — bus factor = 1. Он в отпуске. Инцидент ждёт.
Debt interest.
Команда тратит 40% времени на поддержку кода, который никто не понимает. Не на новые фичи. Не на архитектуру. На поддержку. Это debt interest. Если больше 30% — вы платите больше, чем зарабатываете. Если больше 50% — foreclosure близко.
Unexplainable PR ratio.
Спросите десять авторов: «Что делает этот код?» Двое не могут ответить. Это 20%. Пятеро — 50%. Это unexplainable PR ratio. Потеря замысла, измеренная в числах.
Все пять метрик измеряют владение, а не производство. Они не красивые. Они не растут. Они не радуют руководство. Но они говорят правду.
Можно добавить шестую: cost of incident. Сколько стоит один инцидент? Не только downtime, но и время команды, потерянные клиенты, репутация, упущенные возможности. Если cost of incident растёт быстрее velocity — вы в зоне субпрайма.
5.4. Cost of outage как главная метрика
On-call получил алерт в 3:14. Прод упал. Он открывает дашборд. MTTR — 4 часа. Через час он понимает: модуль биллинга. 1200 строк. Сгенерировано. Логирования нет. Breakpoint не поставить. Он звонит автору. Автор уволен. Звонит архитектору. Архитектор не видел модуль. Инцидент длится шесть часов. Утром CTO спрашивает: «Сколько мы потеряли?» Никто не знает. Никто не считал. Cost of outage — это то, что вы узнаёте после.
Cost of outage — главная метрика, потому что она объединяет всё: качество кода, владение, review, архитектуру, безопасность. Если у вас высокий cost of outage — у вас проблемы во всех слоях.
Из чего он складывается:
потерянная выручка (сколько денег не пришло, пока прод лежал);
время команды (сколько человеко-часов ушло на восстановление);
ушедшие клиенты (сколько не вернулось);
репутация (сколько будущих сделок потеряно);
упущенные возможности (что не выпустили, пока тушили).
Формула (оценка):
text
Cost of Outage = Lost Revenue + Team Hours × Hourly Rate + Churned Clients × LTV + Reputation Damage
Reputation Damage — самая сложная часть. Её нельзя посчитать точно. Но можно оценить: сколько клиентов ушло после инцидента? Сколько не пришло? Сколько партнёров отказалось? Если хотя бы один — это уже число.
В SaaS-компании, которую я знаю, один инцидент на шесть часов стоил $120—180 тыс. (оценка). Из них $40 тыс. — потерянная выручка. $30 тыс. — время команды. $50—100 тыс. — ушедшие клиенты. $10 тыс. — репутация. Экономия на зарплатах за год — $200 тыс. Один инцидент съел половину.
Руководство понимает деньги. Velocity — нет. LOC — нет. «Скорость ИИ» — нет. Деньги — да. Показывайте деньги.
5.5. Как построить дашборд, который не врёт
Тимлид открывает новый дашборд. Слева — старые метрики: velocity, LOC, PR count. Справа — новые: review latency, rework rate, orphan code ratio, debt interest, cost of incident. Слева всё зелёное. Справа — жёлтое и красное. Он смотрит на правую часть. Теперь он знает, что делать.
Дашборд, который не врёт, строится на трёх вещах.
Не убирайте старые метрики. Velocity, LOC, PR count — это не ложь. Это половина правды. Оставьте их. Но добавьте вторую половину. Тогда вы увидите картину целиком. Если убрать старые — вы потеряете язык, на котором говорит руководство. Если оставить только старые — вы потеряете правду.
Метрика без владельца — мёртвая метрика. Если у review latency нет ответственного, она будет расти. Если у orphan code ratio нет ответственного, он будет расти. У каждой метрики — человек. Один. Не «команда». Человек. Иначе метрика превращается в отчёт, который никто не читает.
Метрика без действия — бесполезна. Если review latency растёт, а вы ничего не делаете — зачем измерять? Метрика должна триггерить действие. Если review latency> 30 минут — остановите merge. Если orphan code ratio> 20% — назначьте owner-а. Если debt interest> 30% — пересмотрите план.
Метрика Тип Ответственный Триггер
Lead time DORA DevOps> 3 дней
Deployment frequency DORA DevOps <1 в день
MTTR DORA SRE> 1 часа
Change failure rate DORA SRE> 15%
Review latency AI-specific Tech lead> 30 минут
Rework rate AI-specific Tech lead> 30%
Orphan code ratio AI-specific Архитектор> 20%
Debt interest AI-specific CTO> 30%
Cost of incident Экономика CTO> $10 тыс.
Каждая метрика — с ответственным. Каждая — с триггером. Каждая — с действием. Это не дашборд. Это пульт управления.
Практика: что сделать завтра
Замените одну vanity-метрику на метрику владения. Уберите LOC. Добавьте debt interest. Посмотрите, что изменится в разговоре с руководством.
Посчитайте cost одного инцидента за последние 6 месяцев. Lost revenue + team hours + churned clients + reputation. Сравните с экономией на зарплатах.
Добавьте AI-specific метрики в дашборд. Review latency, rework rate, orphan code ratio, debt interest. Не убирайте DORA. Добавьте к ней.
Назначьте ответственного за каждую метрику. Один человек. Не «команда». Не «все». Человек.
Определите триггер для каждой метрики. Если review latency> 30 минут — остановите merge. Если orphan code ratio> 20% — назначьте owner-а.
Покажите cost of incident руководству в деньгах. Не в процентах. Не в story points. В деньгах.
Метрики
Категория Метрика Формула Триггер
DORA Lead time Время от коммита до прода> 3 дней
DORA Deployment frequency Релизов в день <1
DORA MTTR Среднее время восстановления> 1 часа
DORA Change failure rate Сбоев / релизов> 15%
AI-specific Review latency Время от PR до merge> 30 минут
AI-specific Rework rate Переписанный код / всего> 30%
AI-specific Orphan code ratio Код без owner-а / всего> 20%
AI-specific Debt interest Время на поддержку / общее> 30%
AI-specific Unexplainable PR ratio PR без объяснения / всего> 20%
Экономика Cost of incident Lost Revenue + Team Hours + Churned Clients + Reputation> $10 тыс.
Формула cost of incident (оценка):
text
Cost of Incident = Lost Revenue + (Team Hours × Hourly Rate) + (Churned Clients × LTV) + Reputation Damage
Кейс из trenches
Собирательный кейс. Детали изменены.
B2B SaaS, 25 разработчиков, платформа для управления проектами. Стек: Go + React + PostgreSQL + Kubernetes. Внедрили ИИ-ассистента в январе. К июню velocity выросла на 200%. Дашборд зелёный. Руководство довольно. CTO получил бонус.
В июле — первый инцидент. Прод упал на 4 часа. В августе — второй. В сентябре — третий. Каждый раз чинили дольше. Каждый раз никто не понимал код. Каждый раз on-call не спал.
В октябре CTO попросили посчитать cost of incidents. Он собрал данные.
Первый инцидент: $45 тыс. (потерянная выручка $20 тыс., время команды $15 тыс., ушедшие клиенты $10 тыс.).
Второй: $80 тыс.
Третий: $120 тыс.
Итого за три месяца: $245 тыс. (оценка).
Экономия на зарплатах за год: $300 тыс. (сократили трёх разработчиков).
CTO посмотрел на цифры. Потом на дашборд. Velocity — зелёная. Cost of incidents — $245 тыс. Он закрыл ноутбук. Пошёл к CEO.
Что сделали:
Вернули двух разработчиков из трёх. Не потому что «надо больше людей». Потому что нужен ownership.
Ввели AI-specific метрики в дашборд. Review latency, rework rate, orphan code ratio, debt interest.
Назначили owner для каждого критичного модуля.
Ввели правило: «Нет owner-а — нет merge».
Добавили cost of incident в ежемесячный отчёт для руководства.
Через полгода:
Velocity упала на 40%. Дашборд перестал быть зелёным.
Cost of incidents: $0 за квартал.
Debt interest упал с 45% до 20% (оценка).
Команда перестала дежурить 24/7.
CEO сказал: «Сколько мы потеряли?» CTO ответил: «$245 тыс.» CEO кивнул. Больше не спрашивал. CTO остался на работе. Бонуса не будет.
Красные флаги
«У нас всё зелёное.»
«Метрики не врут.»
«Velocity выросла, значит, всё хорошо.»
«Cost of incident? Мы не считаем.»
«У нас DORA зелёная, зачем что-то ещё?»
«Дашборд красивый, руководство довольно.»
«Мы потом посчитаем.»
Антипаттерн
Как делать НЕ надо:
Считать velocity единственной метрикой.
Игнорировать cost of incident.
Не добавлять AI-specific метрики.
Не назначать ответственного за метрику.
Не определять триггеры.
Показывать руководству проценты вместо денег.
Верить, что «зелёный дашборд = всё хорошо».
Не считать стоимость владения.
Чеклист
□ Заменена хотя бы одна vanity-метрика на метрику владения.
□ Посчитан cost одного инцидента за последние 6 месяцев.
□ Добавлены AI-specific метрики в дашборд.
□ Назначен ответственный за каждую метрику.
□ Определён триггер для каждой метрики.
□ Cost of incident показан руководству в деньгах.
□ DORA не убрана, но дополнена.
□ Дашборд пересматривается раз в квартал.
□ Команда знает, что метрики без действия бесполезны.
□ Руководство понимает разницу между производством и владением.
Что запомнить
Если метрика не включает стоимость владения — она врёт. Не потому что злая. Потому что слепая.
Что почитать
DORA State of DevOps Report — база. Но читайте с вопросом: «Что эти метрики не видят?»
«Accelerate» (Forsgren, Humble, Kim) — как отличать сигнал от шума.
«How to Measure Anything» (Douglas Hubbard) — как считать то, что кажется неизмеримым. Особенно cost of incident.
«The DevOps Handbook» (Kim, Debois, Willis, Humble) — про операционные метрики и их ограничения.
«Thinking in Systems» (Donella Meadows) — про то, почему метрики без контекста врут.
Часть II. Механика отказа: как именно ИИ ломает инженерную систему
Глава 6. Чёрные ящики и потеря замысла
«Код работает. Не трогай.»
— надгробная плита на могиле архитектуры
Тезис
Проблема ИИ-кода не в багах. Проблема в отсутствии модели. Баг можно найти и починить. Потерю замысла — нельзя. Код, который никто не понимает, невозможно эволюционировать: его можно только копировать, обходить или переписывать. Чёрный ящик — это не метафора, а точное описание состояния: вход известен, выход известен, внутренности — нет. Пока это состояние сохраняется, каждое изменение — это русская рулетка.
Ключевые вопросы
Почему сгенерированный код трудно сопровождать, даже если он работает?
Что такое «потеря замысла» и почему она страшнее бага?
Почему комментарии и документация не решают проблему?
Как отлаживать код, если не знаешь логику?
Как вернуть понимание — и можно ли его вернуть вообще?
6.1. Разница между «работает» и «понятно, почему работает»
Вторник, 11:30. Модуль PricingEngine в проде три месяца. Тесты зелёные. Метрики в норме. Никто не трогает. На вопрос «как оно работает?» тимлид пожимает плечами: «Работает — и хорошо».
Через неделю продакт приходит с требованием: добавить скидку для корпоративных клиентов. Тимлид открывает модуль. 1400 строк на TypeScript. Сгенерировано в феврале. Автор промпта перешёл в другую команду. git blame показывает: «AI assistant». Тимлид читает функцию за функцией. Понимает процентов на двадцать. Остальное — «работает, но непонятно как».
Он делает изменение. Тесты проходят. Деплой. Через час — алерт. Скидка применяется дважды для клиентов с определённым тарифом. Никто не знал, что в модуле есть неочевидная связь между тарифом и типом клиента. Она была в коде. Её не было в головах.
Разница простая. Первое — это состояние. Второе — это модель. Модель позволяет предсказать, что будет, если изменить X. Состояние позволяет только наблюдать: сейчас X ведёт себя так. Что будет завтра — неизвестно.
ИИ генерирует состояние. Не модель. Он не объясняет, почему выбрал этот алгоритм, почему здесь reduce, а не map, почему в одном месте мемоизация, а в другом — нет. Он просто выдаёт результат, который проходит тесты. Модель остаётся у модели. Вам — состояние.
Это и есть чёрный ящик. Вход известен: вы даёте данные. Выход известен: вы получаете результат. Внутренности — нет. Пока вы не трогаете — работает. Как только трогаете — вы не инженер. Вы археолог, который копает в темноте.
6.2. Потеря замысла: почему это страшнее бага
Четверг, 15:20. Разработчик открывает модуль NotificationService. Нужно добавить новый канал — push. Он видит: функция sendNotification принимает объект options, в котором есть поле channel со значениями email, sms, webhook. Добавляет push. Мержит.
Через день — инцидент. Push-уведомления уходят клиентам, которые их не заказывали. Оказывается, в options было ещё поле priority, и при priority === ’high’ NotificationService игнорировал channel и отправлял во все каналы, включая те, которые не зарегистрированы. Никто не знал про эту логику. Её не было ни в документации, ни в комментариях. Она была в коде — как мина.
Это потеря замысла. Вы не просто не понимаете код. Вы не понимаете, почему он такой. Замысел — это набор решений, которые кто-то принял. Почему priority перебивает channel? Почему это не задокументировано? Почему это не в тестах? Ответ один: ИИ так сгенерировал. А тот, кто мержил, не спросил почему.
Баг — это отклонение от замысла. Если замысел известен, баг можно найти и починить. Если замысла нет — баг неотличим от фичи. Вы не знаете, что должно было быть. Вы знаете только, что происходит.
Потеря замысла страшнее бага. Баг локален. Потеря замысла — системна. Она распространяется на все будущие изменения. Каждый раз, когда кто-то трогает код, он не может предсказать последствия. Он может только гадать. И молиться.
6.3. Почему комментарии и документация не спасают
Среда, 10:15. Тимлид решает: «Надо задокументировать». Команда садится писать комментарии к сгенерированному коду. Через неделю в модуле PaymentProcessor появляются строки:
typescript
// Обрабатывает платёж
async function processPayment (payment: Payment): Promise
// Проверяем валюту
if (payment.currency === «USD») {
// Конвертируем
const amount = await convert(payment.amount);
// Возвращаем результат
return {status: ’ok’, amount};
}
// Возвращаем ошибку
return {status: ’error’, code: «UNSUPPORTED_CURRENCY»};
}
Комментарии описывают что делает код. Они не объясняют почему. Почему только USD? Почему не EUR? Почему конвертация здесь, а не в вызывающем коде? Почему ошибка возвращается, а не бросается? Почему Result, а не PaymentResult?
Через месяц комментарии устареют. Код изменится, комментарии — нет. Через полгода они станут ложью, которая хуже отсутствия комментариев. Читатель поверит комментарию и ошибётся.
Тимлид попробовал три подхода. Первый — комментарии. Умерли через месяц. Второй — документация. Умерла через полгода. Третий — тесты. Выжили, потому что их нельзя забыть: они падают в CI.
Документация умирает по той же причине. Она описывает интерфейс. Замысел живёт в голове. Если головы нет — документация становится гробницей для мёртвых решений.
Что выживает:
Тесты, которые проверяют поведение, а не реализацию. Не «функция вызывает convert», а «при USD-платеже сумма конвертируется по курсу на момент запроса». Тест — это замысел, записанный на языке, который нельзя забыть.
ADR (Architecture Decision Records). Не «что делает код», а «почему мы решили так». Один файл — одно решение. Дата. Контекст. Альтернативы.
Owner, который помнит. Не документация. Человек. Пока он здесь.
Комментарии — это надгробные плиты. Тесты — это живые свидетели.
6.4. Отладка в темноте: где ставить breakpoint
Пятница, 2:47. Прод упал. Алерт в модуле RecommendationEngine. On-call открывает код. 2000 строк на Python. Сгенерировано. Логирование — одна строка: logger.info («recommendation generated»). Ни параметров, ни стека, ни контекста.
Он ставит breakpoint. Запускает. Функция проходит точку. Идёт дальше. Он ставит второй breakpoint. Та же история. К третьему он понимает: он не отлаживает. Он перебирает. Как обезьяна, которая нажимает кнопки в надежде, что загорится лампочка.
Это отладка в темноте. Вы не знаете, где искать. Вы можете только перебирать. Как в старом анекдоте: «Ищу ключи не там, где потерял, а там, где светло». Только у вас нет даже светлого места.
В сгенерированном коде нет якорей для отладки. Нет комментариев, которые подскажут «здесь важная логика». Нет логов, которые покажут путь. Нет метрик, которые укажут на аномалию. Есть только код и ошибка.
Тимлид попробовал три вещи. Первая — логирование на входе и выходе каждой значимой функции. Вторая — метрики, а не только логи. Третья — runbook для модулей, которые падают в 3 ночи. Все три выжили. Потому что их нельзя забыть: они падают в CI или в 3 ночи.
Отладка — это не про инструменты. Это про понимание. Без понимания инструменты бесполезны.
6.5. Как вернуть понимание
Можно ли вернуть замысел, если он потерян? Частично. Полностью — нет. Но можно восстановить достаточно, чтобы код перестал быть чёрным ящиком.
Археология. Выделите один модуль. Возьмите человека, который его трогал последним — или того, кто вообще не трогал. Попросите написать на бумаге, что делает каждая функция. Не читая код. По памяти. Потом сравните с кодом. Разница покажет, где замысел потерян.
Контракты. Для каждой публичной функции напишите контракт: вход, выход, инварианты, ошибки. Не «функция обрабатывает платёж». А «функция принимает платёж в валюте X, возвращает сумму в USD, бросает InvalidCurrency, если валюта не поддерживается». Контракт — это минимальная модель. Он не объясняет, почему так, но объясняет, что должно быть.
Тесты на поведение. Каждый контракт превращается в тест. Не тест реализации, а тест поведения. После этого изменения можно делать безопасно: тесты поймают отклонение от модели.
ADR на каждое решение. Почему это так? Почему не иначе? Не для каждого if, а для каждого значимого выбора. Через год это будет единственный способ узнать замысел.
Owner. У модуля должен быть человек. Не «команда». Человек. Он не обязан помнить всё. Но он обязан отвечать. И когда он уходит — он передаёт контекст. Не «вот код, разберись». А «вот контракты, вот тесты, вот ADR, вот что я знаю».
Понимание нельзя купить. Его можно только построить. И это долго. Быстрее, чем rewrite. Дешевле, чем foreclosure. Но всё равно долго.
Практика: что сделать завтра
Проведите археологию одного модуля. Возьмите модуль, который трогали последним. Попросите разработчика написать по памяти, что он делает. Сравните с кодом. Разница — карта потерянного замысла.
Напишите контракты для трёх критичных функций. Вход, выход, инварианты, ошибки. Без этого изменения — это гадание.
Проверьте логирование в критичных модулях. Если лог — одна строка logger.info («done»), вы не сможете отлаживать. Добавьте вход и выход каждой значимой функции.



