Мы привыкли бороться с «мусором» в коде — временными костылями, устаревшими методами, забытыми конфигами. Но что если можно проектировать систему так, чтобы она сама чистилась от ненужного, минимизируя технический долг прямо в процессе работы? В статье попробую показать, что это не миф, а вполне реальная практика, основанная на архитектурных паттернах, «самоочищающихся» механизмов и немного наглой инженерной фантазии.
Архитектура с встроенным сроком годности
Большая часть кода умирает не потому, что он плох, а потому что его контекст устаревает. Протокол меняется, бизнес-логика переписывается, API обрастает новым поведением. В итоге в проекте живут десятки функций-«призраков», которые никто не вызывает, но всем страшно удалить.

Один из подходов, который мне довелось применять, — встроенный срок годности кода. Представьте, что каждый метод или endpoint в API живёт ограниченное время: например, 6 месяцев. Если он не используется реальными пользователями или не продлевается вручную через конфигурацию — он автоматически вычищается системой.
Это можно реализовать очень просто. Например, в Python с использованием декоратора:
import datetime import functools def expires_on(expiry_date): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): if datetime.date.today() > expiry_date: raise RuntimeError(f"Функция {func.__name__} устарела и удалена.") return func(*args, **kwargs) return wrapper return decorator @expires_on(datetime.date(2025, 12, 31)) def legacy_payment_method(): return "Старый платежный метод работает... пока"
Таким образом, сам код становится «одноразовым». Он либо обновляется, либо умирает. А если настроить CI/CD так, чтобы устаревшие функции автоматически выпадали из сборки, то система буквально сама вычищает собственные залежи.
Выглядит радикально? Да. Но лучше пусть упадёт тест, чем пол-команды месяцами будет спорить, можно ли удалить этот древний метод.
Мусор как сервис: зачем системе уметь выносить сама себя
Один из самых неприятных видов мусора — это временные зависимости: библиотеки, которые «прикрутили на время», старые миграции базы данных, скрипты для «однократного запуска». Обычно всё это превращается в кладбище внутри репозитория.
В какой-то момент я начал относиться к мусору как к сервису. То есть создавать в системе отдельный слой, задача которого — выносить ненужное. Например, сервис, который раз в неделю проверяет, какие миграции старше N месяцев, и переносит их в архив. Или скрипт-уборщик, который автоматически удаляет неиспользуемые зависимости, сверяясь с lock-файлом.
На Java это можно реализовать как «демон-чистильщик»:
import java.nio.file.*; import java.time.*; import java.util.stream.*; public class CleanupService { private static final Path MIGRATIONS_DIR = Paths.get("db/migrations"); public static void main(String[] args) throws Exception { try (Stream<Path> files = Files.list(MIGRATIONS_DIR)) { files.filter(Files::isRegularFile) .filter(p -> isOld(p, 180)) .forEach(CleanupService::archive); } } private static boolean isOld(Path file, int days) { try { FileTime lastModified = Files.getLastModifiedTime(file); return lastModified.toInstant() .isBefore(Instant.now().minus(Duration.ofDays(days))); } catch (Exception e) { return false; } } private static void archive(Path file) { System.out.println("Архивирую: " + file); // Переместить в архивную папку } }
Здесь идея не в самом коде (он игрушечный), а в принципе: система сама ухаживает за собой, так же как операционная система подчищает временные файлы.
Когда такие чистильщики встроены в архитектуру, команда перестаёт бояться «нагромождений прошлого». Код превращается в живой организм, у которого есть не только рост, но и естественная деградация.
Самоочищающиеся контракты
Самая сложная часть мусора — это человеческие обещания, которые превращаются в костыли. Например, «мы оставим этот endpoint на всякий случай» или «пусть эта функция поживёт, вдруг пригодится».
Чтобы решить проблему, можно встраивать в архитектуру самоочищающиеся контракты. Это когда любая часть системы должна сама доказывать, что она нужна.
Как это работает:
-
Endpoint считается живым только если у него есть активные запросы за последние 30 дней.
-
Библиотека остаётся в зависимостях только если её функции реально вызываются в runtime.
-
Конфигурация сохраняется только если значение используется хотя бы одним модулем.
Пример на Go: сервис, который регулярно проверяет доступность API-методов и убирает мёртвые:
package main import ( "fmt" "time" ) type Endpoint struct { Name string LastUsage time.Time } func (e Endpoint) IsAlive(thresholdDays int) bool { return time.Since(e.LastUsage).Hours() < float64(thresholdDays*24) } func main() { endpoints := []Endpoint{ {"legacyLogin", time.Now().AddDate(0, -2, 0)}, {"newOAuthLogin", time.Now()}, } for _, e := range endpoints { if e.IsAlive(30) { fmt.Printf("Endpoint %s жив\n", e.Name) } else { fmt.Printf("Endpoint %s мёртв — удаляем\n", e.Name) } } }
Технически это несложно. Сложно — решиться доверить системе принимать такие решения. Но зато эффект колоссальный: мусор просто не успевает накапливаться.
Подытожив
Код, который сам себя убирает, — это не магия и не утопия. Это всего лишь принцип: каждая сущность в системе должна иметь встроенный механизм старения и удаления. Не важно, это функция, endpoint, библиотека или конфигурация.
Если система может сама себя чистить, разработчики начинают думать о будущем иначе. Вместо того чтобы бояться «удалить лишнее», они доверяют архитектуре — и тратят силы не на уборку, а на развитие.
И, да, впервые, когда CI уронит билд из-за «просроченной функции», вы будете злиться. А потом поймёте, что лучше один упавший тест, чем год жизни в мусорном коде.
ссылка на оригинал статьи https://habr.com/ru/articles/940218/
Добавить комментарий