No Legacy Policy
Политика работы с устаревшим кодом при рефакторинге и изменениях.
Git — это backup. Удалённый код можно восстановить из истории. Не нужно сохранять старые версии "на всякий случай".
1. Summary
Проблема: При рефакторинге и изменениях AI-ассистенты (и разработчики) часто оставляют:
- Старые re-exports и aliases для "совместимости"
- Закомментированный код с пометками
// removed,// deprecated - Дублирование логики (старая + новая версия)
- Backward compatibility shims для внутреннего кода
Последствия:
- Кодовая база засоряется мёртвым кодом
- Рефакторинг занимает в 2-3 раза больше времени
- Контекст LLM тратится на чтение бесполезного кода
- Новые разработчики путаются в "что актуально"
Решение: Полностью удалять устаревший код сразу. Git хранит всю историю.
2. Quick Reference
| Ситуация | Правильно | Неправильно |
|---|---|---|
| Переименовали функцию | Удалить старую, обновить все вызовы | export { newName as oldName } |
| Переименовали файл | git mv, обновить импорты | Оставить старый файл с re-export |
| Изменили API response | Обновить фронтенд сразу | Добавить legacyField для совместимости |
| Удалили функционал | Удалить полностью | // removed: old feature code |
| Рефакторинг сервиса | Заменить атомарно | Оставить OldService + NewService |
| Исправили баг | Исправить | // fixed: was doing X, now Y |
| Изменили типы | Обновить везде | type OldType = NewType alias |
3. Q&A
Переименования и aliases
Q: Мы переименовали функцию/файл. Оставить ли старый export?
A: Нет. Обнови все места использования сразу.
// ❌ НЕПРАВИЛЬНО: оставлять alias
// user.service.ts
export { getUserProfile as getUser }; // "для совместимости"
// ❌ НЕПРАВИЛЬНО: файл-прокси
// old-path.ts
export * from './new-path'; // redirect
// ✅ ПРАВИЛЬНО: просто переименовать и обновить импорты
// Использовать git mv для сохранения истории
git mv src/old-name.ts src/new-name.ts
// Затем обновить все импорты
Почему:
- IDE умеет "Find and Replace" по всему проекту
- TypeScript покажет ошибки компиляции если что-то пропустили
- Aliases накапливаются и создают путаницу
Q: У нас монорепо с несколькими packages. Как синхронизировать переименования?
A: Изменения в shared/ автоматически триггерят сборку зависимых packages. Обнови всё в одном PR.
# 1. Переименовать в shared
git mv shared/src/types/old.ts shared/src/types/new.ts
# 2. Обновить импорты везде
# VSCode: Cmd+Shift+H → Find/Replace in Files
# 3. Проверить type-check для всех packages
pnpm type-check
# 4. Коммитить всё вместе
Изменение формата данных
Q: Мы изменили формат API response. Нужен ли shim для старого формата?
A: Нет, если клиент под вашим контролем. Обновите фронтенд в том же PR.
// ❌ НЕПРАВИЛЬНО: backward compatibility
return {
data: newFormat,
// legacy support
oldField: newFormat.renamedField, // удалить после миграции
items: newFormat.entries, // deprecated
};
// ✅ ПРАВИЛЬНО: обновить контракт и клиента вместе
// backend: вернуть новый формат
return { entries: [...] };
// frontend: обновить использование
const { entries } = await api.getData(); // было: items
Исключение: Rust webhook API — см. секцию Exceptions.
Q: Мы добавили новое обязательное поле в Prisma модель. Как мигрировать?
A: Создать миграцию с default value, обновить код.
// ❌ НЕПРАВИЛЬНО: оставить optional "временно"
model User {
newField String? // TODO: make required after migration
}
// ✅ ПРАВИЛЬНО: сразу required с default
model User {
newField String @default("default_value")
}
После миграции данных — убрать @default если он больше не нужен.
Рефакторинг "in progress"
Q: Рефакторинг большой, не успеваю за один PR. Можно оставить старый код временно?
A: Да, но с чёткими правилами:
- Feature flag — если нужно A/B тестирование
- Отдельная ветка — если рефакторинг занимает несколько дней
- Atomic commits — разбить на независимые части
// ❌ НЕПРАВИЛЬНО: два сервиса в production
class OldQuestService { ... }
class NewQuestService { ... } // WIP
// ✅ ПРАВИЛЬНО: рефакторить атомарно
// Один коммит = одна логическая единица
// Если сервис большой — рефакторить по методам
Правило: В main ветке не должно быть OldX/NewX дублирования.
Q: Что если рефакторинг сломает что-то, а старый код нужен для отката?
A: Git revert существует именно для этого.
# Откатить конкретный коммит
git revert <commit-hash>
# Или вернуться к предыдущему состоянию файла
git checkout <commit-hash> -- path/to/file.ts
Не нужно держать "backup" в коде — это работа системы контроля версий.
Удалённый код и комментарии
Q: Удалили функционал. Оставить комментарий что тут было?
A: Нет. Git blame и commit message содержат эту информацию.
// ❌ НЕПРАВИЛЬНО
// removed: legacy achievement system (2024-01-15)
// was: calculateLegacyAchievements()
// ❌ НЕПРАВИЛЬНО
/*
* DEPRECATED: This function was replaced by newFunction()
* Keeping for reference, delete after 2024-02-01
*/
function oldFunction() { ... }
// ✅ ПРАВИЛЬНО: просто удалить
// Информация сохранена в git history
// git log --all -p -- path/to/file.ts
Q: Исправили баг. Оставить комментарий о том, что было неправильно?
A: Только если логика неочевидна и комментарий предотвращает повторение ошибки.
// ❌ НЕПРАВИЛЬНО: история изменений
// fixed: was returning null, now returns empty array
// ❌ НЕПРАВИЛЬНО: очевидная логика
// NOTE: using Math.floor to round down (not Math.round!)
const level = Math.floor(xp / 100);
// ✅ ПРАВИЛЬНО: неочевидное бизнес-правило
// Minimum 1 scrap — prevent zero rewards from rounding
const reward = Math.max(1, Math.floor(base * multiplier));
Правило: Комментарий должен объяснять почему, а не что было раньше.
4. Decision Tree
5. Exceptions
Rust Webhook API
Rust плагин деплоится отдельно на игровые сервера. Изменения в webhook API требуют координации:
| Изменение | Подход |
|---|---|
| Новое поле в response | Добавить как optional, плагин игнорирует неизвестные поля |
| Новый event type | Плагин должен поддерживать, координировать релиз |
| Изменение существующего поля | Требует версионирования или синхронный релиз |
| Удаление поля | Сначала сделать optional в плагине, потом удалить |
// Webhook response — backward compatible добавление
return {
success: true,
questsUpdated: [...],
// Новое поле — плагин старой версии просто игнорирует
notifications: [...], // added in v2.1
};
Database Migrations
Prisma миграции необратимы в production. Будь осторожен с:
- Удалением колонок (сначала убедись что код не использует)
- Переименованием (используй два шага: add new → migrate data → remove old)
6. Anti-patterns
Re-export для "совместимости"
// ❌ types/index.ts
export { UserProfile } from './user';
export { UserProfile as User } from './user'; // legacy alias
export { UserProfile as IUser } from './user'; // "interface style"
// ✅ types/index.ts
export { UserProfile } from './user';
// Один тип — одно имя. Обновить все использования.
Файл-прокси после переноса
// ❌ services/old-location.ts
// This file was moved to new-location.ts
// Keeping for backward compatibility
export * from './new-location';
// ✅ Просто удалить old-location.ts
// git mv + обновить импорты
"Временный" fallback
// ❌
function getConfig() {
// New config format
if (process.env.NEW_CONFIG) {
return parseNewConfig();
}
// Legacy fallback — remove after migration
return parseLegacyConfig();
}
// ✅ Мигрировать сразу
function getConfig() {
return parseNewConfig();
}
TODO комментарии без срока
// ❌
// TODO: remove this after refactoring
// TODO: migrate to new API
// FIXME: temporary workaround
// ✅ Или исправить сейчас, или создать issue
// Если создаёшь issue — указать номер:
// TODO(#123): optimize query after index migration
7. Checklist перед PR
- Нет
export { X as Y }aliases для совместимости - Нет файлов только с re-exports (
export * from) - Нет закомментированного кода
- Нет
// removed,// deprecated,// legacyкомментариев - Нет
OldX/NewXдублирования сервисов/типов - Нет
TODO: remove afterбез конкретной даты/issue - Все импорты обновлены (нет unused imports)
- Type-check проходит без ошибок
Related
- Domain Structure — эталон структуры домена
- Logging Guidelines — стандарты логирования