Вопрос, на который нет инвойса
У руководителя, который отвечает за сроки и бюджет, всегда под рукой три цифры: счёт за инфраструктуру, лицензии и фонд оплаты труда. Четвёртой нет нигде — той, что показывает, сколько труда съедает уже написанный код, просто продолжая существовать. Это и есть стоимость владения кодом — TCO, применительно к разработке.
Дефицит этой цифры чувствуется в каждом квартальном разговоре. Заявка на новых людей обосновывается словами «мы тонем в поддержке», встречный вопрос «в чём именно?» повисает — и всем ясно, что ответ существует, но его никто не собрал. Пока цифры нет, любое «нет» головного счёта звучит как экономия, а любое «да» — как капитуляция.
Стоимость владения не попадает ни в один инвойс, потому что размазана: час здесь, день там, и ни одна позиция по отдельности не дотягивает до бюджетного разговора. Счёт за хостинг существует — поэтому кажется, что он и есть стоимость владения. Не он. Но прежде чем показывать, как считать по-настоящему, разберём, почему первый порыв не работает.
Тупик: один номер на весь код
Первый порыв — агрегат. «Сколько нам стоит техдолг?» — и дальше обычно одно из двух: сессия, где команда выставляет баллы, или инструмент, который сканирует код и выдаёт индекс. Оба пути дают число, число показывают на встрече — и на этом всё заканчивается.
Из устройства самого расчёта следует, почему число мёртвое.
У него нет валюты. Баллы и индексы не конвертируются в бюджет. В споре за ресурсы инвойс побеждает оценку «на глаз», потому что инвойс проверяем, а оценка — нет.
Он ни к чему не привязан. Из «долг большой» не следует, что делать в понедельник: агрегат не указывает ни на модуль, ни на человека, ни на решение.
И он усредняет то, что усреднять нельзя. По устройству систем стоимость владения концентрируется там, где чаще всего меняют и где чаще всего ломается: горячий модуль — это тот, через который идут изменения продукта, или тот, что стоит на пути отказов. Остальное — холодный код, который лежит и не напоминает о себе. Среднее по горячему и холодному не описывает ни то, ни другое, а раз так — единый номер на кодовую базу не годится даже как ориентир.
Есть и третий вариант первого порыва: реестр боли — бэклог, куда каждый пишет, что его раздражает. Он честнее баллов и бесполезнее: боль не конвертируется в приоритет, реестр только растёт.
И бытовое: агрегат никто не пересчитывает. Оценка, сделанная однажды, тихо устаревает, а переделывать её не хочется — ведь первый раз она ни к чему не привела.
Выход из тупика: считать не всё, а горячее; не в баллах, а в часах; не по памяти, а по следам.
Модель: часы по следам, которые система оставляет сама
Единица учёта — модуль или сервис: у него есть имя, границы и владелец. Или нет владельца — и это тоже результат замера, а не ошибка учёта. Кодовая база целиком не нужна: нужны несколько модулей, которые чаще всего трогают и чаще всего роняют. Как найти кандидатов, не считая всё: спросить дежурных, что падает; посмотреть, где за окно больше всего PR; спросить старожилов, чего они боятся касаться. Три вопроса — и список готов.
Слагаемых четыре, и все берутся из того, что система и команда оставляют сами: история в git, тикеты, логи дежурств.
Чтение. Каждому новому человеку в модуле приходится его понять. Замер: кто за окно наблюдения впервые сделал в модуле содержательное изменение и сколько времени прошло от первого касания до первого смёрженного. Это прокси цены понимания. Строка не тает со временем — люди ротируются, и каждый новый платит за чтение заново; редкий, но переходящий из рук в руки модуль обходится дороже, чем кажется.
Поддержание жизни. Изменения, которые не меняют поведение: обновления зависимостей, миграции, починка флакающих тестов, правки конфигов. Если тикеты не размечены по типам, годится грубый способ: прочитать заголовки всех смёрженных PR модуля за окно и разложить на «изменили» и «поддерживали». На один модуль уходит вечер, тулинг не нужен.
Инциденты. Часы дежурных, относящиеся к модулю. Если дежурства не логируются по модулям — это тоже результат: первый пункт списка улучшений уже готов.
Согласование. Часы ревью на PR модуля и повторяющиеся созвоны, которые существуют только потому, что модуль непонятен. Ревью — оплаченные часы, которые не учитывает почти никто; если они нигде не фиксируются, одно окно ручного замера даст норму, дальше её можно применять.
Сумма — часы за окно. Почему не сразу деньги: ставки меняются, а часы сравнимы между окнами; перевод в рубли — последний шаг, когда нужна строчка для бюджета. Само окно — любое, за которое у вас жива история: от пары релизов до всего, что сохранилось.
Вторая чаша — ценность модуля за то же окно. Точная цена тут не нужна и даже вредна: достаточно грубой шкалы «приносит деньги / делает возможным то, что приносит / назвать нечего». Третий ответ — это уже ответ.
Правило решения. Если владение дороже ценности, дверей три, и рефакторинг — не первая. Удалить: стоимость владения становится отрицательной, вы просто перестаёте платить. Ужать: меньше кода, зависимостей и интеграций — меньше поверхности. Заморозить: перестать обновлять, изолировать, ограничить круг тех, кто может войти. Заморозка — управляемое состояние, а не капитуляция: у замороженного модуля деградация предсказуема. Рефакторинг — ставка на будущие изменения, и она требует прогноза, а не привычки «дорогой модуль — значит рефакторим». Холодному коду рефакторинг не нужен — ему нужно, чтобы его не трогали.
Для руководителя здесь главный выход: заявка «нам нужно ещё людей» становится проверяемой — вот модуль, вот часы, вот что произойдёт с ними, если модуль ужать или убрать.
Две строки модель не видит, хотя владеют вами сильнее прочих. Первая — платформа: Kubernetes, взятый под небольшую команду, сам становится горячим модулем с постоянными платежами — кластер обновляют, диагностируют, пытаются понять; мы разбирали это в материале про Kubernetes для команды из двенадцати человек. Вторая — стек: выбор языка добавляет строку владения в найме и в замене людей, её мы считали в тексте про найм под редкий язык.
Что игнорировать
Затраты на создание. Они уплачены. Решение «оставить или удалить» сравнивает будущие платежи с будущей ценностью; сколько стоило написать, не аргумент ни за сохранение, ни против. Дорогой в разработке модуль, который больше ничего не даёт, — не актив, а абонемент, за которым вы продолжаете ходить.
Баллы и индексы — по причинам из тупика, одной строкой.
Строки кода и «стоимость строки». Метрика смешивает горячее с холодным и приглашает к игре: можно удалить код, не изменив ничего, и метрика обрадуется.
Счёт за инструменты как главную строку. Его считают первым, потому что он единственный приходит инвойсом. Легко посчитать — не значит посчитать то, ради чего всё затевалось.
Точную оценку «ценности кодовой базы». Ложная точность здесь вреднее грубой шкалы: она создаёт видимость объективности там, где работает простой вопрос «назовите, что модуль даёт».
Опись всех модулей сразу. Модель жива, пока считает горячее; тотальная инвентаризация — тот же агрегат, только дорогой и долгий.
Микроуровень. Спорить по отдельным функциям — шум. Единица решения — модуль.
Позиция редакции
Стоимость владения кодом нужно вести по модулям, в часах, из следов системы — и по дорогим модулям принимать решение в порядке «удалить, ужать, заморозить», оставляя рефакторинг для случаев с обоснованным прогнозом изменений. Полученная строчка должна стоять в бюджетном разговоре рядом с фондом оплаты труда, а не в презентации про качество: это деньги, а не добродетель. Калибровка разовая, дальше рутина.
Мы неправы, если распределение у вас ровное. Проверка та же, что и сам метод: возьмите наугад несколько модулей, прогоните по следам за одно окно. Разброса нет, всё стоит примерно одинаково — значит, горячих точек не существует, по-модульный учёт для вас накладные расходы, и агрегат подойдёт не хуже. Вторая граница: если следов нет — тикеты не привязаны к коду, дежурства не логируются, — то модели нечего есть, и первое вложение нужно не в подсчёт, а в гигиену следов.
Проверить нашу позицию можно тем же вечером, что уходит на саму модель. Пожалуй, это самый дешёвый способ с нами поспорить.