Сталкивался ли legacy проектами
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Опыт работы с legacy проектами
Да, абсолютно. За 10+ лет я регулярно работал с legacy системами, и это одна из самых ценных частей моего опыта.
Характерные проблемы legacy
Обычно в legacy проектах встречаются:
- Отсутствие типизации: код написан на JavaScript без TypeScript
- Слипшаяся архитектура: монолит без разделения на слои, бизнес-логика размазана по контроллерам
- Невозможное тестирование: классы жестко зависят друг от друга, нет dependency injection
- Технический долг: старые версии Express, нет инструментов линтинга
- Отсутствие документации: никто не помнит, как это работает
Мой подход к рефакторингу legacy
Шаг 1: Анализ
Перед любыми изменениями я:
- Запускаю существующие тесты (если есть)
- Изучаю точки входа и критичные пути
- Определяю, какие части используются интенсивно
# Смотрим зависимости
npm list
# Ищем потенциально проблемные места
grep -r "require(" src/ | grep -v node_modules
**Шаг 2: Добавляю тесты
Это критично! Тесты — твоя подушка безопасности при рефакторинге:
// Тестируем существующий функционал ДО изменений
describe('UserController', () => {
it('should return user by id', async () => {
const user = await userController.getUser(123);
expect(user.id).toBe(123);
});
});
Основной принцип: не рефакторь то, что не покрыто тестами.
**Шаг 3: Введение TypeScript постепенно
Не переписываю всё сразу! Перехожу постепенно:
// jsconfig.json или tsconfig.json с checkJs: true
// Добавляю .ts файлы рядом со старым кодом
// старый код: user.js
module.exports = { getUser: (id) => {...} };
// новый код: user-service.ts
export interface IUserService {
getUser(id: number): Promise<User>;
}
export class UserService implements IUserService {
async getUser(id: number): Promise<User> {
return await this.repository.findById(id);
}
}
Постепенно переноси зависимости на новый TypeScript код.
**Шаг 4: Извлекаю бизнес-логику
Выделяю сервисы из контроллеров:
// ДО: логика в контроллере
app.get('/users/:id', async (req, res) => {
const userId = req.params.id;
const user = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
const orders = await db.query('SELECT * FROM orders WHERE user_id = $1', [userId]);
const stats = { totalOrders: orders.length };
res.json({ user, stats });
});
// ПОСЛЕ: логика в сервисе
class UserService {
constructor(private db: Database) {}
async getUserWithStats(id: number) {
const user = await this.getUserRepository().findById(id);
const orders = await this.getOrderRepository().findByUserId(id);
return {
user,
stats: { totalOrders: orders.length }
};
}
}
app.get('/users/:id', async (req, res) => {
const result = await userService.getUserWithStats(req.params.id);
res.json(result);
});
**Шаг 5: Вводим Dependency Injection
Это позволяет заменять зависимости в тестах:
// ДО: жесткая зависимость
class UserService {
private db = new Database(); // ПЛОХО!
async getUser(id) {
return this.db.query(...);
}
}
// ПОСЛЕ: DI
class UserService {
constructor(private db: IDatabase) {} // хорошо!
async getUser(id) {
return this.db.query(...);
}
}
// При тестировании
const mockDb = jest.fn();
const service = new UserService(mockDb);
Стратегии для особо сложных legacy систем
Strangler Pattern — медленно замещаем старый код новым:
// API Gateway перенаправляет запросы
app.get('/api/users/:id', async (req, res) => {
// Если это новый функционал — идём в новый микросервис
if (req.query.v === '2') {
return res.json(await newUserService.getUser(req.params.id));
}
// Иначе в старый код
return res.json(await legacyUserService.getUser(req.params.id));
});
Seams Model — добавляем точки разделения без изменения кода:
// Оборачиваем зависимость в функцию
class UserService {
getDatabase() {
return db; // может быть переопределено в тестах
}
async getUser(id) {
return this.getDatabase().query(...);
}
}
Реальный пример из опыта
Работал с Node.js API, написанным на Express без типизации:
- Добавил TypeScript с checkJs
- Постепенно конвертировал критичные файлы
- Ввел Repository pattern для доступа к БД
- Добавил unit-тесты (coverage > 80%)
- Внедрил чистую архитектуру (domain → application → infrastructure)
Результат: код стал поддерживаемым, новые фичи разрабатываются в 3x раз быстрее.
Ключевые принципы при работе с legacy
- Boy Scout Rule: оставляй код лучше, чем ты его нашел
- Не спешить: рефакторинг — это постепенный процесс
- Тесты первыми: прежде чем менять — покрой тестами
- Маленькие шаги: коммитай часто, пул реквесты маленькие
- Документируй свои решения: почему ты переписал этот код
Legacy проекты — не враг, а возможность применить системное мышление и архитектурные знания.