Автоматическая проверка кода для PHP

в 5:48, , рубрики: code review, continuous integration, php, Песочница, тестирование, метки: , ,

Разрешите представить Вам перевод статьи Johannes Schmitt Automated Code Reviews for PHP. Лично мне она помогла несколько иначе взглянуть на процесс разработки и тестирования своих приложений. А оригинальный подход автора к тестированию, как минимум, заслуживает внимания.
Если вам тоже интересно, добро пожаловать под кат.

С тех пор как появился Trevis, вы можете в мгновение ока внедрить непрерывную интеграцию во все свои PHP-проекты. Это помогает не только улучшить качество кода, но и существенно упрощает поддержку библиотек, предоставляя информацию о сборке прямо в запрос на обновление(pull request) и, тем самым, уменьшает время получения обратной связи. Travis очень хорош, но, как и другие инструменты тестирования, страдает от наследственной болезни — что бы что-то сделать ему нужны тесты. Готовь биться об заклад, что у вас нет ни одного проекта честно покрытого тестами на 100% или, даже, близко к этому. Это я еще надеюсь, что тесты вы пишите.

Как вам возможно известно, я поддерживаю значительное число плагинов(bundles) для Symfony2 и самостоятельных PHP-библиотек. И благодаря сообществу(спасибо ребята, так держать) я постоянно получаю запросы на обновление в свои репозитории. Некоторые из запросов совершенно бесполезные, некоторые заслуживают внимания, некоторые можно добавлять в основную ветку. Но как бы тщательно не проверялся запрос, время от времени случается так, что добавляется то что не работает или работает, но не всегда.

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

<?php

class UserProvider
{
    /** @return User|null */
    public function loadUser($username) { /** ... */ }

    public function refreshUser(User $user)
    {
        if (null === $user = $this->loadUser($user->getUsername())) {
            throw new RuntimeException(
                sprintf('User "%s" was not found.', $user->getUsername()));
        }

        return $user;
    }
}

И так, метод refreshUser получает объект класса User при помощи метода loadUser и возвращает этот объект. А если объект не найден, то бросает исключение. Вроде бы все просто, но так ли это на самом деле? И если уж я об этом спрашиваю, то видимо нет и многие из вас уже заметили ошибку. Внутри блока if $user равен null и мы не можем вызвать у него метод getUserName. Что бы находить такого рода ошибки я испробовал несколько простых решений, но довольно быстро становилось очевидно что они работают только в очень специфичных случаях. Мне было нужно что-нибудь получше.

Type Inference of PHP Code

Я потратил довольно много времени вникая в концепции потока данных, потока управления и абстрактной интерпретации. Что само по себе выглядит довольно сложно и выходит за рамки этой статьи. Но позвольте мне привести всего несколько примеров и дать вам общее представление об этих концепциях.

Анализ потока управления(Control Flow Analysis)

Анализ этого потока позволяет определить в каком порядке будут выполняться различные блоки вашего кода.

<?php

function fooBar($i) {
    if ($i > 0) {
        echo 'foo';
    } else {
        echo 'bar';
    }
}

Для этого кода поток управления будет выглядеть так:
Автоматическая проверка кода для PHP
Мы начинаем в if, затем двигаемся к «foor» или «bar» и, наконец, выходим. Само по себе нам это вряд ли чем-то поможет, но это послужит основой для следующего шага.

Анализ потока данных(Data Flow Analysis)

Анализ потока данных позволяет определить как изменяется контекст выполнения пока мы движемся по схеме которую определили в анализе потока управления.

<?php

$x = null; // $x здесь null.

if ($y) {
    $x = new DateTime();
    $x->format(); // тут все хорошо, мы ведь знаем что $x это экземпляр DateTime
} else {
    $x = 0;
}

$x->format(); // $x экземпляр DateTime или число "integer", в зависимости от этого
                    // метод может быть вызван а может и нет

Не зная порядок выполнения кода, мы может заключить только то, что $x может быть null, число или DateTime. Но нам это не поможет выяснить может ли быть вызван метод format.

Абстрактная интерпретация(Abstract Interpretation)

Для нашего случая эта концепция сводится к вопросу «Какие предположения мы можем сделать, если знаем результат условного выражения?». Давайте взглянем на другой пример:

<?php

class Foo
{
    private $logger;

    public function __construct(Logger $logger = null)
    {
        $this->logger = $logger;
    }

    public function doSth()
    {
        if (null !== $this->logger) {
            $this->logger->log('doing sth');
        }
    }
}

В данном случае «условным выражением» будет null !== $this->logger. Если это условие истинно, то наш вопрос можно перефразировать так: «Если выражение null !== $this->logger истинно, то какое предположение можно сделать на счет $this->logger?» Как мы уже выяснили, $this->logger может быть null или Logger. Но благодаря абстрактной интерпретации мы можем быть уверены что внутри блока «if» $this->logger всегда будет экземпляром Logger, следовательно, метод может быть вызван.

Автоматическая система проверки

Какой от всего этого толк, спросите вы. В начале статьи я сказал, что моей целью было создание автоматической системы проверки кода. И я думаю что сейчас она готова для широкого использования и обсуждения. Я протестировал своей системой ведущие PHP библиотеки, такие как, Zend Framework 2, Symfony2, Doctrine, Propel и многие другие. Она содержит более 100 правил проверки, которые вы можете использовать и конфигурировать. Если у вас есть PHP-проект на Github вы можете легко попробовать. Просто залогинтесь http://jmsyst.com/automated-code-reviews и выберете нужный репозиторий. А если не понравиться, можете выключить в любое время.

Если теперь кто-то скажет, что PHP-программисты не слишком серьезно относятся к качеству кода, отправляйте их их ко мне.

Автор: tmvrus

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js