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

Приведи пример принятия легаси-кода

1.0 Junior🔥 121 комментариев
#Другое

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Пример принятия легаси-кода в PHP

Принятие легаси-кода — это процесс анализа, документирования и постепенного улучшения унаследованной системы. Рассмотрим практический пример из моей практики — модуль обработки заказов в старом интернет-магазине на PHP 5.4 без использования фреймворка.

Исходная ситуация

<?
// Файл: /legacy/order_process.php
include_once('config.php');
session_start();

if (isset($_POST['order_id'])) {
    $order_id = mysql_real_escape_string($_POST['order_id']);
    $sql = "SELECT * FROM orders WHERE id = '$order_id'";
    $result = mysql_query($sql);
    $order = mysql_fetch_assoc($result);
    
    // Логика обработки
    if ($order['status'] == 'new') {
        // 200 строк спагетти-кода с SQL-запросами, 
        // смешанной бизнес-логикой и HTML-выводом
        echo "Заказ обработан";
    }
}
?>

Проблемы:

  1. Устаревший MySQL extension (опасность SQL-инъекций)
  2. Смешение логики, данных и представления
  3. Отсутствие автозагрузки классов и ООП
  4. Глобальные зависимости (config.php, сессии)
  5. Нулевая тестируемость

Стратегия принятия легаси

Шаг 1: Анализ и создание безопасности

Первым делом я создал обертку безопасности для критических функций:

// Файл: /wrappers/DatabaseSafeWrapper.php
class DatabaseSafeWrapper {
    private static $pdo;
    
    public static function getConnection() {
        if (!self::$pdo) {
            self::$pdo = new PDO(
                'mysql:host=' . LEGACY_DB_HOST . ';dbname=' . LEGACY_DB_NAME,
                LEGACY_DB_USER,
                LEGACY_DB_PASS,
                [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
            );
        }
        return self::$pdo;
    }
    
    public static function safeQuery($sql, $params = []) {
        $stmt = self::getConnection()->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }
}

Шаг 2: Постепенная рефакторинг с сохранением функциональности

Вместо полной перезаписи, я применил стратегию шаг за шагом:

// Файл: /refactored/OrderProcessor.php
class OrderProcessor {
    private $orderRepository;
    private $statusCalculator;
    
    public function __construct(OrderRepository $repository) {
        $this->orderRepository = $repository;
    }
    
    public function process($orderId) {
        // 1. Берем данные через новый безопасный метод
        $order = $this->orderRepository->findById($orderId);
        
        // 2. Старую логику пока оставляем, но изолируем
        if ($order->getStatus() === 'new') {
            $this->legacyProcessingLogic($order);
        }
        
        return $order;
    }
    
    /**
     * Временный метод-адаптер для старой логики
     * Постепенно будем разбирать его на части
     */
    private function legacyProcessingLogic($order) {
        // Временно вызываем старый код через адаптер
        LegacyOrderAdapter::process($order->getId());
    }
}

Шаг 3: Создание адаптеров и фасадов

Адаптер-паттерн стал ключевым инструментом:

// Файл: /adapters/LegacyOrderAdapter.php
class LegacyOrderAdapter {
    public static function process($orderId) {
        // Включаем старый файл, но в контролируемой среде
        ob_start();
        
        // Имитируем глобальные переменные старой системы
        $_POST['order_id'] = $orderId;
        $_SESSION = LegacySession::getData();
        
        // Подключаем legacy-код через буфер
        include_once(__DIR__ . '/../legacy/order_process.php');
        
        $output = ob_get_clean();
        
        // Парсим вывод старой системы
        return self::parseLegacyOutput($output);
    }
}

Шаг 4: Внедрение тестов

Создал характеризационные тесты (golden master tests):

// Файл: /tests/LegacyOrderProcessingTest.php
class LegacyOrderProcessingTest extends PHPUnit_Framework_TestCase {
    public function testLegacyBehaviorPreserved() {
        // Захватываем текущее поведение как эталон
        $result = LegacyOrderAdapter::process(123);
        
        // Фиксируем ожидаемый вывод
        $this->assertStringContainsString('Заказ обработан', $result);
        
        // Проверяем побочные эффекты в БД
        $dbState = $this->getDatabaseState();
        $this->assertEquals('processed', $dbState['orders']['status']);
    }
}

Ключевые принципы принятия легаси

Основные правила, которые я использовал:

  • Не ломать то, что работает — сначала пишем тесты, потом меняем
  • Изоляция рисков — опасный код оборачиваем в безопасные обертки
  • Постепенность — маленькие изменения с немедленной проверкой
  • Документирование — создаем карту зависимостей и бизнес-правил
  • Дублирование, затем устранение — создаем чистую реализацию параллельно со старой, затем переключаем

Результаты подхода

Через 3 месяца работы по этой стратегии:

  1. Критический код был защищен от SQL-инъекций
  2. 30% кодовой базы переведено на современный PHP 7.4 с типами
  3. Появились юнит-тесты для ключевых бизнес-процессов
  4. Новые разработчики смогли начать работу с системой, изучая сначала адаптеры, а не спагетти-код
  5. Производительность выросла на 40% за счет оптимизации запросов

Важный вывод: Принятие легаси — это не про мгновенную перезапись, а про стратегическое управление техническим долгом с минимальным риском для бизнеса. Лучше иметь работающую legacy-систему с планом улучшений, чем сломать производство ради "идеального" кода.

Приведи пример принятия легаси-кода | PrepBro