Разрешите представить Вам перевод статьи 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';
}
}
Для этого кода поток управления будет выглядеть так:
Мы начинаем в 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