№ 074
27 июля 2026
11 мин чтения

Правила, которые нельзя нарушить: как сделать разработку сходящейся

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

Правила, которые нельзя нарушить: как сделать разработку сходящейся

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

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

Сначала снимите ложный диагноз

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

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

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

Три настоящие причины

1. Правила живут в прозе

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

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

2. Нет единой границы записи

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

Симптом, по которому это видно снаружи: правило нарушается каждый раз в новом месте. Починили в кнопке «Сохранить» — вылезло в drag-and-drop; починили там — вылезло в массовой операции. Тикеты выглядят как разные дефекты, а причина одна.

3. Регрессионная сеть есть, но ни к чему не подключена

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

Это ровно то состояние, в котором «чиним одно — ломаем другое» становится неизбежным: обратная связь о поломке приходит не через 30 секунд от теста, а через сутки от заказчика.

И отдельно — для тех, у кого код пишет ИИ-агент

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

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

Как надо: шесть решений

1. Разделите нормативный документ и карту кода

Это два разных жанра, и путать их нельзя:

  • Техзадание — что должно быть верно. Согласовано с заказчиком, меняется только вместе с его решением.
  • Карта кода (описание алгоритма, архитектурный обзор) — как сделано сейчас. Растёт вместе с кодом, нормативной силы не имеет.

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

Правило спора: код разошёлся с ТЗ — прав ТЗ, расхождение оформляется задачей. Не наоборот.

2. Различайте запрет и предпочтение

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

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

3. Сделайте реестр инвариантов кодом

Не абзац, не комментарий, а список в отдельном модуле. У каждого правила:

  • идентификатор — им называют нарушение в логе и в разговоре;
  • ссылка на пункт ТЗ — чтобы правило можно было оспорить в правильном месте;
  • область действия — кого ограничивает. Часто оказывается, что запрет адресован автоматике, а человек вправе его обойти осознанно (и наоборот);
  • чистая функция проверки — без обращения к экрану и глобальному состоянию, чтобы её можно было звать и в бою, и в тесте.

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

4. Сведите запись к одному шлюзу

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

  1. собирает предлагаемое изменение;
  2. прогоняет реестр инвариантов;
  3. при нарушении отказывает и называет правило;
  4. и только потом пишет.

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

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

5. Тестируйте правила, а не кнопки

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

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

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

6. Сделайте прогон обязательным — и быстрым

Тесты, которые не блокируют мерж, — это документация, а не защита. Три требования:

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

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

Тикет должен производить правило, а не патч

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

Задача, названная по имени файла или экрана («вот здесь поправьте вот это»), производит патч. Задача, названная правилом, производит правило. Минимальный шаблон, который окупается:

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

И правило разбиения: одна задача — одно правило. Задача из пяти пунктов закрывается по первому, остальные четыре возвращаются через неделю новыми задачами — это и есть механизм, который создаёт ощущение бесконечности.

Как понять, что процесс стал сходящимся

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

  1. Доля повторов по месту. Какой процент задач приходит туда, где за последние 30 дней уже была задача. Если этот процент не снижается месяц за месяцем — сходимости нет, что бы ни говорили ощущения.
  2. Доля смысловых дублей. Процент задач, у которых в истории есть близкий по смыслу предшественник. Считается эмбеддингами, но с обязательной калибровкой: сначала измерьте, какое сходство дают случайные пары задач из вашего корпуса, и только потом выбирайте порог. Абсолютные значения косинуса не переносятся между корпусами и моделями — переносится процедура.
  3. Время от правки до следующей задачи по тому же месту. Медиана в часах. Короткая медиана — признак тугой петли без фильтра: правка уходит в бой, дефект возвращается вопросом.
  4. Красных тестов в карантине. Число, которое обязано убывать, и график, который стыдно показывать растущим.
  5. Доля изменений, прошедших через обязательный чек. Здесь допустимо только 100%.

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

Порядок внедрения

Ставьте дешёвое первым — иначе не доживёте до дорогого:

  1. Гейт с карантином — часы работы, эффект сразу. Даже без единого нового теста вы перестаёте ломать то, что уже покрыто.
  2. Иерархия документов и точка входа — часы. Особенно если код пишет ИИ-агент.
  3. Шаблон задачи и правило «сначала падающий тест» — привычка, а не работа.
  4. Реестр инвариантов — дни. Начните с двух-трёх правил, которые возвращались чаще всего; полный список не нужен.
  5. Шлюз записи — дни, и это самый рискованный шаг. Его нельзя делать одним изменением вместе с реестром: если поедет, разбирать будет нечего.
  6. Тест «входы × правила» — день после реестра.

Что вы получаете

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

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

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

← Все выпуски
Выпуск № 074