За двадцать лет в профессии я успел поработать тестировщиком, разработчиком, 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_in — 25%. Если от класса зависит много других классов, его страшно менять.
depth — ещё 25%. Глубокие цепочки превращают чтение кода в путешествие по графу.
centrality — 15%. Это сигнал появления объектов, через которые проходит слишком много путей. Такие классы постепенно становятся «центром мира».
После этого 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 → DiscountPolicydepth = 3
есть зависимость от родителя
логика размазана
Попробуем упростить
def premium_discount(user):
if user.is_premium:
return 0.15
def orders_discount(user):
if user.orders > 10:
return 0.10depth ≈ 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 возможно вас заинтересуют вещи которые мы туда выкладываем.
И да, спасибо, что дочитали мою первую статью до конца. Похоже, инвайт на Хабр, который ждал своего часа больше десяти лет, наконец-то его дождался. Дальше планирую написать цикл статей про промпт инжинирию, управление контекстом и гайд по тому, как писать хорошие скиллы для агентов, так что подписывайтесь, надеюсь - будет интересно :)





















