javascript

Код в эпоху AI: как перестать бороться за качество и начать контролировать деградацию

  • четверг, 18 июня 2026 г. в 00:00:12
https://habr.com/ru/articles/1029170/

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

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

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

Мы ускорились и не заметили, что потеряли

AI дал нам ощущение, которого раньше почти не было в разработке - лёгкость: фичи появляются быстро, задачи закрываются легко, код пишется почти без трения. И первое время это похоже на магию. Ты смотришь на velocity и думаешь: «Вот теперь-то мы действительно ускорились». Но магия всегда что-то забирает взамен. Она забрала сопротивление, то самое, которое раньше заставляло остановиться и подумать: «А точно ли так стоит делать?». Теперь код появляется быстрее, чем успевает сформироваться сомнение, система начинает меняться, не резко, почти незаметно она становится… тяжелее.

Это не ломается, это «оседает»

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

Мы задаём вопрос, на который нет смысла отвечать

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

Две силы, которые мы не замечаем

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

Ты открываешь функцию, сначала всё понятно, потом появляется ещё одно условие, потом ещё одно.. Ты читаешь дальше - и вдруг ловишь себя на том, что перестал держать всё в голове. Не потому что сложно, а потому что слишком «плотно».

Есть код, который длинный, но читается легко, а есть код, который короткий - но «давит» и не даёт «дышать».

def get_user_name(user):
    if user:
        if user.profile:
            if user.profile.first:
                if user.profile.last:
                    return f"{user.profile.first} {user.profile.last}"
    return "Unknown"

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

def get_user_name(user):
    profile = user and user.profile
    if not profile:
        return "Unknown"
    return format_name(profile.first, profile.last)

И вдруг становится легче, как будто в комнате открыли окно.

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

Баланс, который нельзя почувствовать на скорости

Интересно, что команды обычно чувствуют момент, когда что-то идёт не так, но чувствуют постфактум.

Когда:

  • изменения начинают занимать больше времени

  • баги становятся страннее

  • обсуждения длиннее

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

Нам не хватает зрения

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

Но раз мы понимаем проблему, то почему бы не попробовать ее решить.

До этого момента мы говорили про ощущения:

  • «код тяжёлый»

  • «архитектура мешает»

Но если оставить это на уровне ощущений - ничего не изменится.

Хорошая новость в том, что обе силы можно описать вполне инженерно и измерить, назовем их - Refactoring Pressure (RP), и Overengineering Pressure (OP).

Refactoring Pressure (RP): давление сложности

RP отвечает на вопрос:

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

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

Поэтому RP состоит из двух частей:

RP = 0.6 × Peak(max, p90, loc) + 0.4 × Base(density, loc)

Peak - это давление от самых сложных мест, он смотрит не только на max_complexity, но и на p90_complexity.

combined = max_complexity × 0.6 + p90_complexity × 0.4Peak = 100 × (1 - e^(-0.08 × combined)) × scale

Почему здесь max и p90? Потому что один монстр на complexity 40 - это плохо, но если p90 тоже высокий, значит проблема уже не локальная. Это не одна больная функция, а стиль всей кодовой базы.

Base - это фоновое давление системы:

Base = 100 × (1 - e^(-0.02 × density × scale))

А density считается как:

density = (total_complexity / loc) × 100

Тут важно уточнить, что LOC (lines of code) - это чистые строчки кода, без учета визуального форматирования.

Почему же важна density, представьте две функции:

Первая:

  • 200 строк

  • 10 условий

Вторая:

  • 20 строк

  • те же 10 условий

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

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

Overengineering Pressure (OP): давление архитектуры

OP отвечает на другой вопрос:

насколько архитектура стала источником сложности?

Сначала считается score на уровне класса / структуры:

class_score = (
    0.35 × fan_out_norm +
    0.25 × fan_in_norm +
    0.25 × depth_norm +
    0.15 × centrality_norm
) × 100

Здесь веса важны.

fan_out весит больше всего — 35%, потому что класс, который зависит от многих других, трудно понимать и тестировать изолированно.

fan_in25%. Если от класса зависит много других классов, его страшно менять.

depth — ещё 25%. Глубокие цепочки превращают чтение кода в путешествие по графу.

centrality15%. Это сигнал появления объектов, через которые проходит слишком много путей. Такие классы постепенно становятся «центром мира».

После этого OP считается уже на уровне проекта:

OP = (
    0.4 × coupling_norm +
    0.6 × avg_class_score_norm
) × 100

То есть общий coupling важен, но не доминирует. Главный вклад - 60% - дают средние class-level scores. Это логично: проект может иметь умеренную связанность в среднем, но при этом содержать несколько архитектурных узлов, которые делают изменения болезненными.

Как связаны OP и RP

Чтобы визуализировать разницу и лучше уловить суть, давайте посмотрим на пример, конечно он будет надуманным, но поможет ощутить разницу

class PremiumPolicy(OrdersPolicy):
    def calculate(self, user):
        if user.is_premium:
            return 0.15
        return super().calculate(user)

На первый взгляд всё нормально, но если посмотреть на граф связей:

PremiumPolicy → OrdersPolicy → DiscountPolicy
  • depth = 3

  • есть зависимость от родителя

  • логика размазана

Попробуем упростить

def premium_discount(user):
    if user.is_premium:
        return 0.15

def orders_discount(user):
    if user.orders > 10:
        return 0.10
  • depth ≈ 1

  • связи минимальны

  • логика локальна

В итоге OP уменьшается, но:

  • если бороться с OP - можно увеличить RP

  • если сильно снижать RP - можно вырастить OP

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

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

Представим два проекта:

A: RP = 20, OP = 20
B: RP = 5,  OP = 35

Формально:

Total(A) = 40
Total(B) = 40

Но это разные системы.

  • В первом - баланс

  • Во втором - сильный перекос в архитектуру

И второй вариант на практике будет ощущаться хуже.

Итоговый score. Ключевая идея: штраф за дисбаланс.

Теперь когда мы понимаем насколько значим баланс, становится понятно, что итоговый score должен строится не только на сумме метрик, но и на разнице между RP и OP.

В упрощённом виде:

Score = (RP + OP) + k × |RP - OP|

где:

  • первая часть - общее давление

  • вторая - штраф за перекос

Как это читается

  • если RP ≈ OP - система сбалансирована

  • если один сильно выше - появляется штраф

Например:

A: RP = 20, OP = 20 → Score = 40
B: RP = 5,  OP = 35 → Score = 40 + penalty → хуже

Система редко «умирает» от одного фактора. Она умирает от того, что либо логика становится слишком плотной, либо структура становится слишком тяжёлой, но чаще - от того, что эти вещи разъезжаются и создают дисбаланс. Обычно видя такие проекты мы говорим "у Васи там весь проект сплошная вермишель" и это сильный перекос в RP или "у Феди там такой оверинжиниринг, что потеряться можно" и это сильный перекос в OP.

И что же дальше?

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

Постепенно экспериментов становилось больше, данных тоже. Скрипт разросся в полноценный проект, который сегодня называется strictacode. Сейчас без него не обходится практически ни один наш проект, разрабатываемый с помощью AI. Мы используем его для контроля метрик в CI, формирования планов улучшений, как инструмент который помогает AI-агентам принимать более осознанные решения для рефакторинга (в комплекте есть skill для агента который поможет сделать анализ).

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

Если тема вам близка - попробуйте strictacode, покрутите цифры, покритикуйте идеи и поделитесь своим опытом. Возможно, у вас есть более удачные способы борьбы с этой проблемой. А мне будет очень интересно их обсудить.

Так же буду рад вас видеть в нашем тг канале QAriumCommunity возможно вас заинтересуют вещи которые мы туда выкладываем.

И да, спасибо, что дочитали мою первую статью до конца. Похоже, инвайт на Хабр, который ждал своего часа больше десяти лет, наконец-то его дождался. Дальше планирую написать цикл статей про промпт инжинирию, управление контекстом и гайд по тому, как писать хорошие скиллы для агентов, так что подписывайтесь, надеюсь - будет интересно :)