Привет, Хабр!

Наши коллеги из beeline cloud подкинули интересную статью для перевода про разработку на PHP, плохие практики и не только. Это история о том, как правила чистого кода могут подорвать его фактическое качество. Материал содержит много рассуждений на эту тему и будет полезен всем, кто только начинает свой путь в разработке. Приятного чтения!

Невыдуманная история одного разработчика

Недавно я искал новую работу, тщательно просеивая море вакансий, каждая из которых стремилась привлечь к себе внимание и побудить кандидата отправить резюме.

В потоке бесчисленных и одинаковых объявлений одно предложение действительно выделялось. Оно было написано как-то иначе. По-особенному, без всего этого рекламно-продающего посыла. Компания искала не просто разработчика, ей нужен был настоящий мастер своего дела. «Мы ценим чистый код», — заявляли они.

На краткий миг я испытал искреннюю радость. Но, как всегда, уже в следующем абзаце обнаружился подвох: «Каждый класс, который мы пишем, ограничивается сотней строк».

Фото: Ben Griffiths / Unsplash.com

Оцениваем длину класса

Для понимания, мой основной язык — PHP, а вакансия предназначалась как раз для PHP-разработчиков.

И здесь я бы хотел заметить, что определить на глаз оптимальный размер класса не так уж просто. Каждый язык программирования имеет свои нюансы и особенности. Существуют слабо типизированные и сильно типизированные языки. Одни поддерживают многопоточность, другие — однопоточные. Список можно продолжать еще долго. Кроме того, существуют еще и стандарты.

Уже почти десять лет я не сталкивался с Java, но, если я правильно помню, там принято помещать открывающую скобку в конец строки. В PHP же принято выносить эту скобку в отдельную строку.

Вот простой пример:

public class Calculator {
    public double add(double number1, double number2) {
        return number1 + number2;
    }
}
class Calculator
{
    public function add(float $number1, float $number2): float
    {
        return $number1 + $number2;
    }
}

На первый взгляд, эти фрагменты несут одинаковую функциональность. Но, если следовать стандартам кодирования обоих языков, версия для PHP окажется длиннее, чем ее аналог для Java. И это даже без учета дополнительных строк, таких как импорты, комментарии и пропуски строк для улучшения читаемости — все это способствует увеличению длины класса.

Вы скажете, что к длине класса такие вещи не имеют отношения. Но кому-то может всерьез показаться, что раз уж пустые строчки есть в файле, их тоже надо учитывать.

Что случится, если связать разработчику руки строгим правилом — не более ста строк кода на файл? Давайте посмотрим на возможные результаты.

Поощряем плохие практики

Определение функции в PHP, по стандарту, требует не менее четырех строк — одна для заголовка функции, две для открывающей и закрывающей скобок, и, по крайней мере, еще одна смысловая строка в теле.

Если количество строк ограничено, возникает соблазн сэкономить место за счет написания избыточно сложных функций.

Кроме того, можно запросто свалиться в пропасть сомнительных стратегий, подходов и методов. Вы спросите, каких? Вот мой «любимый» способ сэкономить место в файле: длиннющие и чрезмерно сложные выражения в одну строку.

Приведу здесь фрагмент из моей интеграционной библиотеки. Это часть класса, отвечающего за создание и управление доступом к удаленным ресурсам:

class Address implements AddressContract, Stringable
{
    //...

    /**
     * Checks if the collection contains parameters with
     * the given names and validates them. This method
     * accepts an arbitrary number of arguments, each of
     * which should be a string representing a parameter
     * name.
     *
     * @param string ...$names The names of the parameters
     * to check for.
     *
     * @return bool Returns true if all parameters are
     * found, false otherwise.
     */
    public function hasValid(string ...$names): bool
    {
        foreach ($names as $name) {
            if (!isset($this->parameters[$name]) || !$this->parameters[$name]->isValid()) {
                return false;
            }
        }
        return true;
    }

    //...
}

Для понимания контекста, Address в моей системе предусматривает такие параметры, как {id} в https://example.com/articles/{id}. Код проверяет, содержит ли экземпляр Address допустимые параметры.

Здесь есть весьма понятный PHPDoc (кстати, он еще в процессе разработки), а сам код прост в освоении. Он перебирает все указанные имена и проверяет, что каждое из них существует и является корректным. В противном случае возвращается false. Если оператор if не выдаст true, то на выходе тоже будет true.

А для экономии места я мог бы написать вместо этого такой код:

class Address implements AddressContract, Stringable
{
    //...

    public function hasValid(string ...$names): bool
    {
        return array_reduce($names, fn($carry, $name) => { return $carry && isset($this->parameters[$name]) && $this->parameters[$name]->isValid(); }, true);
    }

    //...
}

Ура, мы сэкономили 5 строчек!

Пусть сотрудники тратят время впустую

Зацикленность на длине класса попросту контрпродуктивна.

Вместо того, чтобы направить свою энергию на создание высококачественных компонентов, разработчики считают строки и придумывают костыли, чтобы уложиться в отведенные рамки.

Хотя кто-то может возразить, что процесс подсчета строк можно автоматизировать, это обоюдоострый меч. Автоматизация может непроизвольно поспособствовать развитию дурной практики, поскольку разработчику наверняка захочется перехитрить систему.

Даже если она призвана обеспечить соблюдение лучших практик на свете, это будет вечная игра в кошки-мышки; разработчики будут придумывать новые способы обхода действующих правил, чтобы сэкономить время.

При этом не стоит забывать, что автоматизация должна либо каким-то образом срабатывать отдельно, либо быть частью конвейера CI/CD.

Так или иначе, у разработчиков не будет мгновенной обратной связи по соответствию кода внутренним стандартам, а это еще один камень в огород производительности их труда.

Клубок из крошечных классов

Принудительное разбиение классов на составные части иногда может привести к непреднамеренному созданию того, что я люблю называть «крысиным королем»: клубка из крошечных классов, которые не могут жить друг без друга.

Это, пожалуй, самая распространенная проблема, и она требует к себе пристального внимания.

При наличии одного исчерпывающего класса пользователям необходимо знать только об этом классе. Однако если разбить его на множество мелких, тесно связанных между собой классов, пользователям придется знать о них всех.

Это удобно, потому что каждый компонент можно заменить. Это оправдано, но только в том случае, если вы на самом деле планируете что-то заменять.

Джон Оустерхаут обращает на это внимание в разговоре о старой системе обработки ввода-вывода в Java. Чтобы эффективно считывать сериализованные объекты из файла, раньше приходилось инстанцировать три вложенных объекта. При этом с первыми двумя практически никогда не нужно было взаимодействовать в явном виде.

// You rarely interact with these two objects,
// except when injecting them into the next one.
FileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);

// You actually interact with this one.
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);

Эта проблема часто возникает из-за злоупотребления принципом единой ответственности. Попытка разбить большой класс на множество мелких может привести к созданию классов, которые сами по себе бесполезны.

Установление жесткого ограничения на длину класса может еще сильнее подтолкнуть к такой архитектуре.

Есть подходы и получше

Подсчет строк в классе сложно качественно отслеживать. Кроме того, он фактически ничего не говорит о качестве самого кода, только о его длине.

Безусловно, большое количество строк может послужить своеобразным маркером — сигналом к тому, что код нуждается в рефакторинге. Однако решения о рефакторинге, основанные только на длине, должны приниматься с осторожностью.

Возьмем в качестве примера класс Model в Laravel. Он насчитывает более 2,4 тыс. строк кода. Несмотря на то, что это может показаться огромным объемом, он намеренно спроектирован таким образом. Такой подход гарантирует, что разработчики смогут extend'нуть базовый класс Model, и вуаля, их собственная модель работает без лишних усилий.

При написании кода в первую очередь следует обращать внимание на его удобство, а не на длину. Старайтесь делать вещи, с которыми пользователь будет работать чаще всего, как можно проще.

Эта статья написана под влиянием моего личного опыта и книги Джона Оустерхаута «Философия проектирования программного обеспечения». Эта книга посвящена глубокому изучению вопросов создания хорошего кода и является обязательной к прочтению для всех, кто всерьез занимается разработкой программного обеспечения.

beeline cloud — secure cloud provider. Разрабатываем облачные решения, чтобы вы предоставляли клиентам лучшие сервисы.

Больше материалов о разработке читайте здесь: