В
прошлом посте, мы начали обсуждать небольшие утилиты, которые, находясь всегда под рукой, способны помочь разработчикам находить в исходном коде проблемные места, позволяют взглянуть на код с другой стороны, и в общем случае помогают быстрее выполнять свои задачи. Сегодня эта тема будет продолжена. На этот раз я бы хотел обсудить три таких инструмента:
Сначала мы поговорим о
PHP_CodeSniffer. Этот инструмент позволяет “понюхать” ваш код на дурные запахи. CodeSniffer незаменим, если вы хотите использовать такую инженерную практику, как code review. Не стоит самостоятельно проверять код на соблюдение принятого code style. Эту работу лучше сделает CodeSniffer. Запустить проверку можно таким способом:
phpcs --standard=PEAR application/classes
FILE: application/classes/legacy.class.php
------------------------------------------------------------------------
FOUND 4 ERROR(S) AND 1 WARNING(S) AFFECTING 5 LINE(S)
------------------------------------------------------------------------
2 | ERROR | Missing file doc comment
3 | ERROR | Missing class doc comment
6 | ERROR | Missing function doc comment
11 | ERROR | You must use "/**" style comments for a function comment
14 | WARNING | Line exceeds 85 characters; contains 89 characters
------------------------------------------------------------------------
Time: 0 seconds, Memory: 3.25Mb
Безусловно существует возможность настроить правила проверки под свои стандарты кодирования. Для этого можно обратиться к
документации, где рассмотрен один простой пример. В этом месте я бы хотел остановиться немного дольше, чем при рассмотрение других инструментов, и показать другой пример для создания своего правила проверки стиля написания кода в PHP_CodeSniffer.
В качестве примера, будет рассмотрена такая практика, как
венгерская нотация (Hungarian Notation). Эта практика должна помочь в понимании программного кода благодаря небольшим префиксам для идентификаторов. Префиксы обычно означают тип данной переменной или тип возвращаемого значения для функций. Например, название
$aUser сразу говорит нам, что данная переменная является массивом. В примере мы будем проверять установлены ли префиксы названий переменных. Саму венгерскую нотацию вы можете использовать по своему усмотрению, я не являюсь ни приверженцем данной практики, ни ее противником, а здесь просто использую ее в качестве примера.
Итак, для начала, мы создадим свой стандарт кодирования, разместив свой каталог в иерархию PHP_CodeSniffer. В моем случае это такой путь:
/usr/share/php/PHP/CodeSniffer/Standards/Example. Здесь мы должны разместить файл
ruleset.xml со следующим содержанием. См.
синтаксис файлов ruleset.
<?xml version="1.0"?>
<ruleset name="Example">
<description>The Example coding standard.</description>
<rule ref="PEAR" />
</ruleset>
Здесь мы позаимствовали все стандарты PEAR, а свой стандарт для проверки использования венгерской нотации для переменных мы поместим в файл
Sniffs/NamingConventions/HungarianNotationSniff.php внутри нашего каталога
Example. Так будет выглядеть примерное содержания для выбранной проверки:
<?php
class Example_Sniffs_NamingConventions_HungarianNotationSniff
extends PHP_CodeSniffer_Standards_AbstractVariableSniff
{
private $_variablePrefixes = array(
'b', //bool
'a', //array
'i', //int
's', //string
'f', //float
'o', //object
'm', //mixed
);
private $_predefinedVariables = array(
'this',
'GLOBALS',
'_SERVER',
'_GET',
'_POST',
'...' //something else
);
/**
* Processes class member variables.
*
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of token.
*
* @return void
*/
protected function processMemberVar(
PHP_CodeSniffer_File $phpcsFile,
$stackPtr
) {
$tokens = $phpcsFile->getTokens();
$memberProps = $phpcsFile->getMemberProperties($stackPtr);
if (empty($memberProps) === true) {
return;
}
$memberName = ltrim($tokens[$stackPtr]['content'], '$');
$scope = $memberProps['scope'];
if (!$this->_isVariablePredefined($memberName) &&
!$this->_isVariablePrefixed($memberName)
) {
$error =
'%s member var "%s" must be prefixed with a type code';
$data = array(
ucfirst($scope),
$memberName,
);
$phpcsFile->addError(
$error,
$stackPtr,
'MemberVarWithoutPrefix',
$data
);
}
}
/**
* Processes normal variables.
*
* @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
* @param int $stackPtr The position of token.
*
* @return void
*/
protected function processVariable(
PHP_CodeSniffer_File $phpcsFile,
$stackPtr
) {
$tokens = $phpcsFile->getTokens();
$variableName = ltrim($tokens[$stackPtr]['content'], '$');
if (!$this->_isVariablePredefined($variableName) &&
!$this->_isVariablePrefixed($variableName)
) {
$error =
'Variable "%s" must be prefixed with a type code';
$data = array(
$variableName,
);
$phpcsFile->addError(
$error,
$stackPtr,
'VariableWithoutPrefix',
$data
);
}
}
/**
* Processes variables in double quoted strings.
*/
protected function processVariableInString($phpcsFile, $stackPtr)
{
//TODO process variables in string
return;
}
/**
* Check if variable is predefined
*
* @param string $variableName Variable name
* @return bool
*/
private function _isVariablePredefined($variableName)
{
return
in_array($variableName, $this->_predefinedVariables);
}
/**
* Check if variable is correctly prefixed
*
* @param string $variableName Variable name
* @return bool
*/
private function _isVariablePrefixed($variableName)
{
return
preg_match(
'/^_?[' . implode($this->_variablePrefixes). '][A-Z]/',
$variableName
);
}
}
?>
Теперь попробуем что-нибудь проверить. Для этого у меня есть тестовый файл
example.class.php с таким содержимом (обратите внимание, что тестовый файл не содержит никаких нарушений стандартов кодирования от PEAR):
<?php
/**
* Example file for PHP_CodeSniffer
*
* PHP version 5
*
* @category Example_File
* @package Example_File
* @author Eugene Khamukhin <khamukhin@gmail.com>
* @license Some free licence
* @link http://blog.yetanother.ru/
*/
/**
* Example class for PHP_CodeSniffer
*
* @category Example_Class
* @package Example_Class
* @author Eugene Khamukhin <khamukhin@gmail.com>
* @license Some free licence
* @link http://blog.yetanother.ru/
*/
class Example
{
private $_cargo;
/**
* Set cargo for space ship
*
* @param ExampleCargo $cargo Cargo to deliver
*
* @return null
*/
public function setCargo($cargo)
{
$this->_cargo = $cargo;
}
/**
* Deliver cargo via Planet Express
*
* @return ExampleCargo
*/
public function diliver()
{
//do some work
}
}
?>
Запускаем проверку на дурные запахи и изучаем результат:
phpcs --standard=Example example.class.php
FILE: /home/khamukhin/phpcs/example.class.php
------------------------------------------------------------------------
FOUND 3 ERROR(S) AFFECTING 3 LINE(S)
------------------------------------------------------------------------
25 | ERROR | Private member variable "_cargo" must be prefixed
34 | ERROR | Variable "cargo" must be prefixed with a type code
36 | ERROR | Variable "cargo" must be prefixed with a type code
------------------------------------------------------------------------
Time: 1 second, Memory: 3.50Mb
Вторая программа, о которой я бы хотел рассказать - это
PHP Depend. Эта утилита вычисляет много различных метрик (о которых можно будет рассказать в отдельной статье), а также строит графики зависимостей пакета.
Использование PHP Depend более актуально в контексте сервера непрерывной интеграции. Например, вот так будет выглядеть запуск PHP Depend в скрипте сборки для Phing:
<!-- ============================================ -->
<!-- Target: run.phpdepend -->
<!-- ============================================ -->
<target name="run.phpdepend">
<phpdepend>
<fileset dir="${build.dir}/application/classes">
<include name="**/*.php" />
</fileset>
<logger type="jdepend-xml"
outfile="${build.dir}/build/jdepend.xml" />
<logger type="jdepend-chart"
outfile="${build.dir}/build/pdepend/dependencies.svg" />
<logger type="overview-pyramid"
outfile="${build.dir}/build/pdepend/overview-pyramid.svg" />
<analyzer type="coderank-mode" value="method" />
</phpdepend>
</target>
Последний инструмент, который будет рассмотрен в данной статье, это
PHPMD - Mess Detector. Данная утилита позволяет проверить исходный код на потенциальные проблемы, не очень оптимальные участки кода, неиспользуемые параметры, методы и т.д. Вот как может выглядеть запуск phpmd:
phpmd classes text codesize,unusedcode,naming
classes/legacy.class.php:3 This class has too many methods,
consider refactoring it.
classes/legacy.class.php:3 The class Legacy has an overall
complexity of 68 which is very high.
The configured complexity threshold is 50.
...
Для этой утилиты также можно настраивать свои правила, а как это сделать можно найти в
документации. Для code review данный инструмент также, как и PHP_CodeSniffer является просто незаменимым.
На этом серия постов про полезные инструменты не завершается, продолжение следует!