Что такое «запахи кода» (code smells)?

«Что такое «запахи кода» (code smells)?» — вопрос из категории Архитектура, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

«Запахи кода» — это поверхностные индикаторы потенциально более глубоких проблем в дизайне или архитектуре программы. Они не являются багами сами по себе, но сигнализируют о нарушениях принципов чистого кода (SOLID, DRY), что затрудняет чтение, тестирование и поддержку.

Распространённые запахи кода и примеры на PHP:

  1. Дублирование кода (Violation of DRY): Одна и та же логика в нескольких местах.

    // Запах
    function calculateCartTotal($items) {
        $total = 0;
        foreach ($items as $item) {
            $total += $item['price'] * $item['quantity']; // Логика расчёта стоимости дублируется
        }
        return $total;
    }
    function generateInvoiceLine($item) {
        return $item['price'] * $item['quantity']; // То же самое здесь
    }
  2. Длинный метод: Метод, который делает слишком много и его сложно понять.

  3. Большой класс (Божественный объект): Класс, который знает и делает слишком много, нарушая принцип единственной ответственности (SRP).

  4. Цепочка вызовов (Feature Envy): Чрезмерное обращение к внутренним данным другого объекта.

    // Запах: класс Customer слишком много знает о внутреннем устройстве Order
    $discount = $customer->getOrder()->getItems()->getLast()->getPromo()->getValue();
  5. Использование "магических" чисел или строк: Непонятные значения прямо в коде.

    // Запах
    if ($user->age > 18) { // Что такое 18?
        // ...
    }
    // Рефакторинг
    const LEGAL_AGE = 18;
    if ($user->age > LEGAL_AGE) {
        // ...
    }

Рефакторинг примера с дублированием:

// Выносим общую логику в отдельный метод/функцию
function calculateItemTotal(float $price, int $quantity): float {
    return $price * $quantity;
}

function calculateCartTotal(array $items): float {
    $total = 0.0;
    foreach ($items as $item) {
        $total += calculateItemTotal($item['price'], $item['quantity']);
    }
    return $total;
}

function generateInvoiceLine(array $item): string {
    $lineTotal = calculateItemTotal($item['price'], $item['quantity']);
    return "{$item['name']}: {$lineTotal} USD";
}