← Назад к вопросам

Сталкивался ли legacy проектами

1.3 Junior🔥 171 комментариев
#Soft skills и опыт работы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Опыт работы с 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 без типизации:

  1. Добавил TypeScript с checkJs
  2. Постепенно конвертировал критичные файлы
  3. Ввел Repository pattern для доступа к БД
  4. Добавил unit-тесты (coverage > 80%)
  5. Внедрил чистую архитектуру (domain → application → infrastructure)

Результат: код стал поддерживаемым, новые фичи разрабатываются в 3x раз быстрее.

Ключевые принципы при работе с legacy

  • Boy Scout Rule: оставляй код лучше, чем ты его нашел
  • Не спешить: рефакторинг — это постепенный процесс
  • Тесты первыми: прежде чем менять — покрой тестами
  • Маленькие шаги: коммитай часто, пул реквесты маленькие
  • Документируй свои решения: почему ты переписал этот код

Legacy проекты — не враг, а возможность применить системное мышление и архитектурные знания.