Самое странное в старых системах — они могут активно развиваться и одновременно деградировать. Команда закрывает задачи, в релизах появляются новые функции, бизнес видит движение. Формально всё нормально.
Но внутри команды уже есть фразы, после которых разговор заканчивается: «Это лучше не трогать», «там когда-то всё упало», «работает же», «давайте сделаем рядом». В этот момент система ещё жива технически, но команда уже начинает относиться к ней как к минному полю. Не как к инструменту, который можно улучшать, а как к механизму, где любое лишнее движение может привести к аварии.
Я не сразу понял, насколько это опасно. Долгое время мне казалось, что такая осторожность — признак зрелости. Люди помнят инциденты, берегут боевую базу, не рискуют без необходимости. В разговоре это звучит как забота о стабильности.
Проблема начинается позже, когда осторожность перестаёт защищать систему и начинает запрещать думать.
История про модуль, который нельзя было трогать
На одном проекте у нас была система расчёта бонусов. Проекту было больше пяти лет. За это время он прошёл несколько этапов внедрения, десятки доработок под бизнес и длинный хвост исправлений. Через систему успела пройти не одна команда.
Сначала её делала команда внедрения. Потом проект остался на одном разработчике, который занимался поддержкой и закрывал срочные запросы. Позже бизнес вырос, задач стало больше, и вокруг проекта снова собрали команду. Когда я подключился, в ней было уже около 10 человек.
Со стороны проект выглядел живым: система росла вместе с бизнесом, появлялись новые правила, отчёты, интеграции и сценарии для пользователей. Но внутри было другое ощущение: общее понимание многих доработок уже пропало.
Не потому что люди были слабые. Просто система слишком долго жила в режиме постоянных изменений. Один разработчик добавлял правило под конкретную акцию. Другой через год исправлял исключение для отдельной группы клиентов. Третий менял порядок расчёта, потому что бизнес поменял условия программы. Четвёртый закрывал ошибку после релиза и оставлял комментарий, понятный только ему самому.
Каждая отдельная правка в моменте была объяснима, но вместе они перестали складываться в понятную схему. Одна доработка — под акцию. Вторая — под исключение. Третья — под возврат. Четвёртая — под ручную корректировку. Через пять лет это уже не набор решений, а слой за слоем накопленная история, которую нельзя восстановить по одному файлу.
Так модуль начисления бонусов превратился в участок, который никто уже толком не понимал. Команда особенно переживала именно за него. На вопрос «как он считает?» обычно не было короткого ответа. Кто-то знал один сценарий. Кто-то помнил, что есть исключения для отдельных клиентов. Кто-то говорил, что часть логики завязана на дату документа. Кто-то вспоминал, что после старой доработки бонусы пересчитывались иначе при возвратах. Но цельной картины не было.
Самое неприятное началось после пары неудачных попыток вмешаться в эту логику. Разработчик менял небольшое условие, а потом всплывало расхождение в другом сценарии. Исправляли начисление для одного типа операции — ломался пересчёт при возврате. Убирали дублирование — оказывалось, что в одном месте оно было не дублем, а старым исключением для конкретного бизнес-правила.
После таких историй в команде закрепилась простая фраза: «Бонусы лучше не трогать». Сначала она звучала разумно. Модуль действительно был критичным. Ошибка в начислении бонусов быстро становилась видимой: клиенты задавали вопросы, менеджеры спорили с цифрами, поддержка получала обращения, бизнес просил срочно объяснить, почему начислилось не так.
Если честно, я поначалу тоже поддерживал эту осторожность. Когда кто-то предлагал привести модуль в порядок, я скорее соглашался с командой: «Сейчас не время. Давайте не будем рисковать релизом». Это выглядело как зрелое решение. Не ломать работающий механизм. Не трогать критичную логику без полной уверенности. Сначала закрыть задачу бизнеса, потом когда-нибудь вернуться к архитектуре.
Но это «когда-нибудь» не наступало. Новые требования продолжали приходить. Нужно было изменить правило начисления для одной группы клиентов. Потом добавить исключение для другой. Потом учесть новую акцию. Потом изменить пересчёт при возврате. И каждый раз команда выбирала более безопасный на вид путь: не менять старую логику, а обойти её.
Где-то добавляли отдельную ветку. Где-то делали предварительную корректировку перед вызовом модуля. Где-то пересчитывали результат после того, как основной механизм уже отработал. Где-то создавали отдельную обработку для частного случая, потому что «в общий модуль лучше не лезть».
К моменту, когда я подключился к проекту, система расчёта бонусов уже жила не как единый механизм, а как набор обходных дорог вокруг старого модуля. Формально всё продолжало работать, но цена росла годами.
Это стало видно в оценках. Задачу, которую раньше обсуждали как правку на 4–6 часов, уже оценивали в 2–3 дня: надо было найти все места начисления, проверить возвраты, ручные корректировки, акции и ночные пересчёты. На обсуждениях команда всё чаще спорила не о том, как правильно сделать, а о том, какой участок лучше не задеть. QA тоже расширял проверки: даже маленькое изменение проходило через 10–12 сценариев, потому что никто не мог назвать точную зону влияния.
Тогда я впервые поймал себя на мысли: мы защищали модуль начисления бонусов от поломки, а в итоге сделали его ещё менее управляемым. И что хуже — мы почти не замечали этого.
Как «не трогать» превращается в архитектуру
В командах страх изменений редко звучит прямо. Никто не говорит: «Я боюсь менять этот код». Обычно говорят иначе: «сейчас не время», «давайте после релиза», «это не в рамках задачи», «лучше добавим отдельный механизм», «там много связей», «надо сначала всё изучить».
Иногда это действительно правда. Не каждый рефакторинг нужен прямо сейчас. Не каждую старую функцию надо переписывать. Боевая база важнее архитектурной красоты. Но есть граница: осторожность защищает систему, пока помогает принимать решения. Страх начинает управлять системой, когда запрещает даже разбираться.
На том проекте мы уже не обсуждали, как устроено начисление бонусов. Мы обсуждали только то, как его обойти. Страх превращался в архитектуру не одним решением, а цепочкой маленьких обходов.
Сначала появлялся запрет: «в основной модуль не лезем». Потом приходило новое требование бизнеса. Его надо было выполнить, поэтому разработчик добавлял отдельную ветку рядом. Через месяц приходило похожее требование — и появлялась ещё одна ветка. Потом кто-то делал обработку, которая корректировала результат уже после начисления, потому что менять сам расчёт было страшно.
Так «не трогаем» превращалось в «дублируем». Не сразу. Без злого умысла. По одному безопасному решению за раз. Именно здесь осторожность меняет природу. Она уже не снижает опасность, а переносит её в будущее и делает дороже.
Если команда говорит: «Давайте сначала покроем это тестами и потом изменим» — система жива. Если команда говорит: «Давайте вообще не будем туда смотреть», задача перестаёт быть инженерной. Команда уже не ищет решение, а выбирает маршрут вокруг опасного места.
Обычно всё начинается с конкретной боли. Не с философии про технический долг, а с правки, после которой команда несколько дней разбирает последствия. Но в памяти команды это часто остаётся не как «мы плохо подготовили изменение». Остаётся другая формула: «менять опасно».
Потом команда начинает искать обходы. Нужно новое правило — не трогаем старый механизм, добавляем новый. Нужна другая логика расчёта — не меняем ядро, оборачиваем сверху. Нужно ускорить отчёт — не разбираем старый запрос и регистры, а заранее складываем результат в отдельный регистр сведений. Нужно убрать дублирование — не сейчас, потому что этот код уже работает в продуктиве.
Каждый такой обход выглядит разумно в моменте. Релиз не сорвали. Бизнес получил функцию. Команда избежала риска. Но в коде остаётся тихий договор с будущим: мы знаем, что это неправильно, но сейчас не будем менять. Через год этот договор уже никто не помнит. Остаётся только код.
Как код становится табу
После нескольких таких историй вокруг отдельных участков системы появляется молчаливый запрет. Не потому что в документации написано «критичный модуль». Не потому что есть ясное объяснение. Просто все знают.
Новый разработчик спрашивает: «Почему бонусы начисляются здесь и ещё пересчитываются в отдельной обработке?» Ему отвечают: «Так исторически сложилось. Не трогай». Он уточняет: «Но здесь ошибка. Если изменить правило в одном месте, второе останется старым». Ответ тот же: «Знаю. Но когда это меняли в прошлый раз, всё упало». И он не трогает.
С этого момента это уже не просто плохой код. Это командное табу. И дальше табу начинает само себя защищать. Новые люди приходят уже не в систему, а в набор запретов. Они не видели исходной аварии, не знают, что именно сломалось, но быстро понимают: спорить с этим участком бесполезно.
После этого команда делает вывод: «Видите, бонусы действительно опасные». Хотя настоящая причина уже другая. Опасность создал не сам механизм. Опасность создали обходы, которые появились из-за страха его менять.
Я видел это не только в бонусах. Так начинают вести себя любые крупные механизмы, которые несколько лет дорабатывали разные разработчики без общего плана. Сначала каждый решал свою задачу. Потом менялись люди, терялся контекст, накапливались исключения. И однажды механизм, который раньше просто работал, превращался в участок, куда команда уже не хотела заходить.
Сначала есть один сложный механизм. Потом вокруг него появляются обходы. Потом обходы становятся частью архитектуры. Потом любое изменение действительно становится опасным. Страх сам создаёт доказательства своей правоты.
Почему это не только техническая проблема
Долгое время я думал, что такие ситуации решаются архитектурой. Нужно больше тестов. Лучше документация. Чище границы модулей. Меньше дублирования. Понятнее владение кодом. Всё это правда.
Но потом я увидел: даже когда появляются тесты и время на рефакторинг, команда не всегда идёт менять старое место. Потому что проблема уже не только в коде. Она в памяти команды.
Разработчик, который однажды сломал боевую базу, потом иначе смотрит на похожие задачи. Он может понимать, что теперь есть тесты. Может понимать, что изменение безопаснее. Но внутри всё равно остаётся вопрос: «А если снова упадёт?»
Руководитель, который один раз объяснялся перед бизнесом за сорванный отчётный период, тоже становится осторожнее. Иногда слишком осторожным. Старший разработчик, который помнит ночной разбор инцидента, начинает защищать команду от повторения боли. Он говорит новичкам: «Не трогайте это». И в моменте он прав.
Но проходит год. Потом второй. Люди меняются. Причина запрета стирается. Остаётся только сам запрет.
Есть старая управленческая притча про обезьян и бананы. Как научный аргумент я бы её не использовал. Но как образ командной памяти она точная.
Клетка. Пять обезьян. К потолку подвязана связка бананов. Под ней стоит лестница. Одна обезьяна голодная. Она подходит к лестнице, чтобы достать банан. Как только она касается лестницы, всех обезьян обливают холодной водой.
Проходит время. Другая обезьяна пытается сделать то же самое. Снова холодная вода для всех. На третий раз воду уже можно отключить. Теперь, как только кто-то идёт к лестнице, остальные сами хватают его и не дают подойти. Никто не хочет снова попасть под холодный душ.
Потом одну обезьяну заменяют новой. Она видит бананы, видит лестницу и идёт наверх. Остальные бросаются на неё. Новая обезьяна не знает про воду. Она не понимает причину. Но быстро запоминает правило: к лестнице нельзя.
Потом заменяют вторую обезьяну. Новая снова идёт к бананам — и её останавливают все. В том числе та, которую самой водой никогда не поливали. Причём с особым энтузиазмом. Так постепенно в клетке оказываются пять обезьян, которых водой вообще не поливали. Но они всё равно не дают никому подойти к лестнице. Почему? Потому что так тут заведено.
В командах это выглядит проще и тише. Никто уже не помнит все детали старых неудачных правок. Никто не может быстро восстановить полную цепочку решений. Но фраза «бонусы не трогаем» продолжает работать. Правило пережило причину. И чем дольше оно живёт без причины, тем сильнее становится. Потому что теперь это уже не решение по конкретному инциденту. Это часть культуры.
Где я сам ошибался
Моя ошибка была в том, что я путал осторожность с управлением. Когда команда говорила «не трогаем», я часто воспринимал это как зрелость. Люди помнят боль, берегут боевую базу, не рискуют без необходимости.
Но управление начинается не с запрета. Управление начинается с вопроса: «Что именно мы боимся сломать?» Если ответа нет, перед нами не риск. Перед нами туман.
На том проекте я слишком долго принимал этот туман как норму. Мы могли сказать: «Бонусы трогать опасно». Но не могли быстро назвать все сценарии, которые надо проверить. Не могли показать полную схему начисления. Не могли объяснить, какие исключения ещё актуальны, а какие остались от старых бизнес-правил.
Это был не осознанный риск. Это была выученная беспомощность. Позже я стал начинать такие разговоры иначе. Не с фразы «давайте перепишем». И не с фразы «ничего не трогаем». Я начинал с инвентаризации страха.
Мы брали участок системы, который команда боялась менять, и разбирали его как обычную инженерную задачу. Что именно может сломаться? Кто это заметит первым? Какие документы или данные пострадают? Какие сценарии надо проверить? Где эта логика дублируется? Кто в команде понимает её лучше всех? Когда последний раз мы реально пытались это менять?
Иногда после такого разговора становилось ясно: да, трогать сейчас нельзя. Сначала нужны тесты, стенд, план отката, отдельное окно и человек, который понимает предметную область. Но иногда выяснялось другое: запрет держится на истории трёхлетней давности, которую уже никто не может восстановить.
И тогда появлялась возможность не героически переписывать всё, а сделать маленький безопасный шаг. Не менять начисление бонусов сразу, а сначала собрать все места, где модуль вызывается. Потом добавить логирование. Потом описать три самых частых сценария. Потом проверить возвраты. Потом убрать один обход. Потом второй.
Не подвиг. Просто возвращение контроля. Каждый такой шаг стоит времени. Иногда день. Иногда два. Нужно разобраться, описать сценарий, проверить, договориться с QA, не сорвать релиз. Но пять маленьких шагов часто дешевле одного большого рефакторинга, который все боятся начать, три раза переносят, а потом срывают из-за срочных задач бизнеса.
Команда начинает верить системе не после большого рефакторинга. Она начинает верить после нескольких маленьких изменений, которые прошли без пожара.
На что я теперь смотрю в старых системах
Когда я прихожу в зрелую систему, я смотрю не только на код. Я слушаю фразы. На обсуждении кто-то говорит: «Это исторически сложилось». Я прошу показать историю. Через десять минут звучит: «Сюда лучше не лезть». Я спрашиваю, что именно сломается. Потом появляется предложение: «Давайте сделаем рядом». И вот тут уже важно остановиться.
Не запретить. Не прочитать лекцию про технический долг. Просто посчитать, сколько таких «рядом» уже есть. Один человек, который отвечает на все архитектурные вопросы, сначала выглядит как сильная опора. Но для команды это уязвимость. Если только он знает, где нельзя наступать, значит, система держится не на понятной архитектуре, а на памяти одного человека.
Отдельно я смотрю на места, где система умеет делать одно и то же несколькими способами. Два механизма уведомлений. Три варианта начисления бонусов. Четыре способа выгрузить данные. Пять мест, где проверяются права.
Это не всегда катастрофа. Иногда у этого есть причина. Но если команда не может объяснить, почему способов несколько, значит, проблема уже не в одном модуле, а в способе принимать решения. Поэтому в старой системе я сначала ищу не «плохой код», а места, вокруг которых команда выстроила запреты. Если их назвать вслух, становится понятнее, где реальная опасность, а где правило давно пережило причину.
Осторожность, которая убивает систему
Система умирает не тогда, когда в ней много старого кода. Старый код сам по себе не страшен. Я видел системы на старых механизмах, которые нормально развивались, потому что команда понимала их устройство и могла менять без паники.
Система умирает, когда команда перестаёт верить, что её можно безопасно изменить. Сначала это выглядит как осторожность. Потом как привычка. Потом как культура. И в какой-то момент архитектура начинает отражать не предметную область, а историю испугов команды.
Здесь скопировали, потому что боялись тронуть ядро. Здесь сделали второй механизм, потому что первый был «опасный». Здесь оставили старую ветку, потому что никто не помнит, кому она нужна. Здесь всё держится на одном человеке, потому что только он знает, где нельзя наступать.
Снаружи это всё ещё система. Внутри — карта старых запретов. Я не думаю, что каждую такую систему надо сразу переписывать. Часто это невозможно и не нужно. Но я теперь считаю важным ловить момент, когда осторожность перестаёт защищать и начинает консервировать проблему.
Со временем выбор сужается. Пока механизм ещё понятен, его можно изменить. Когда вокруг него выросли обходы, остаётся только добавлять новые. Когда даже обходы начинают конфликтовать друг с другом, команда уже не развивает систему, а поддерживает её до замены.
Теперь в таких проектах я первым делом спрашиваю себя и команду: где у нас осторожность ещё защищает систему, а где она уже мешает ей жить? Обычно ответ начинается с простой фразы: «это лучше не трогать».
Вступайте в нашу телеграмм-группу Инфостарт