Skip to main content

No Legacy Policy

Политика работы с устаревшим кодом при рефакторинге и изменениях.

Core Principle

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: Да, но с чёткими правилами:

  1. Feature flag — если нужно A/B тестирование
  2. Отдельная ветка — если рефакторинг занимает несколько дней
  3. 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 проходит без ошибок