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

Ответ

«Запахи кода» — это поверхностные индикаторы потенциально более глубоких проблем в дизайне или архитектуре программы. Они не являются багами сами по себе, но сигнализируют о нарушениях принципов чистого кода (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";
}

Ответ 18+ 🔞

А, ну это же классика, ёпта! Сидишь такой, читаешь чужой код, а от него прямо волнение ебать — пахнет, как будто в программировании только вчера открыли, что функции бывают. Эти «запахи кода» — они как сигнал: «Чувак, тут пиздец, но пока не критичный». Сам по себе код может работать, но читать его — терпения ноль ебать, а через полгода и поддерживать будет охренеть как сложно.

Вот смотри, основные «ароматы», от которых глаза слезятся.

1. Дублирование кода (Нарушение DRY — Don't Repeat Yourself). Это когда одно и то же говно написано в пяти разных местах. Представь, у тебя формула расчёта суммы в корзине и в строке счёта. И ты её тупо копипастишь.

// Запах — чувствуешь, да? Одна и та же хуйня в двух местах.
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. Большой класс (он же «Божественный объект»). Этот класс знает всё: он и пользователя авторизует, и заказы обрабатывает, и отчёты в PDF генерит, и, кажется, уже кофе заваривать начинает. Нарушает первый же принцип SOLID — единственная ответственность. Такой класс — это пизда рулю, чистой воды.

4. Цепочка вызовов (Feature Envy). Когда один объект лезет в сраку другому, вытаскивая данные через длиннющую цепочку методов. Смотри, какой пиздец:

// Класс Customer вдруг решил, что ему жизненно важно знать про последнюю промо-акцию последнего товара в заказе.
// Да ты кто такой, сука? Прекрати!
$discount = $customer->getOrder()->getItems()->getLast()->getPromo()->getValue();

Малейшее изменение в классе Order — и всё, приехали, всё посыпется. Доверия к такому коду — ебать ноль.

5. «Магические» числа и строки. Просто цифры или тексты, вставленные в код без пояснений. Смотришь и думаешь: «А это нахуя 18?».

// Запах — что за хуй с горы?
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) {
        // Используем нашу новую функцию. DRY, детка!
        $total += calculateItemTotal($item['price'], $item['quantity']);
    }
    return $total;
}

function generateInvoiceLine(array $item): string {
    // И тут тоже её используем. Красота!
    $lineTotal = calculateItemTotal($item['price'], $item['quantity']);
    return "{$item['name']}: {$lineTotal} USD";
}

Вот и всё. Чувствуешь, как запах выветрился? Код стал чище, логичнее, и когда надо будет поменять формулу расчёта (например, добавить налог), ты поправишь её в ОДНОМ месте, а не бегать по всему проекту, как угорелый.