Статический анализ кода

В чём вообще проблема

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

Различие в форматировании может усложнять чтение кода и его отладку для других разработчиков. А мёртвые участки, наличие копипасты и переусложненный код, кроме того, что тоже усложняет чтение и отладку, может ещё и нести в себе скрытые проблемы.

За всем этим нужно как-то следить, даже если вы работаете над проектом в одиночку, так как требования и функционал постоянно меняются, проект живёт.

И как маленький бонус к проблемам, работа над форматированием кода создаёт лишние правки в системе контроля версий.

Какие есть решения

Есть несколько способов унифицировать и проверять код на проекте. Подробнее о каждом.

1. Править код тимлиду

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

Плюсы

  • Качество. Поскольку проверка выполняется человеком, причём с опытом, то качество проверки довольно хорошее.
  • Время внедрения. Плюс не надо ничего настраивать, старший разработчик может начать анализировать код, просто увидев его, например, в репозитории.
  • Гибкость. Человек не машина, и его поведение ничем не ограничено.

Минусы

  • Внимательность. Каким бы классным ни был специалист, он всё же человек и может пропускать какие-то детали.
  • Цена. Вторым минусом является то, что для анализа и рефакторинга относительно большого проекта нужны часы каждый раз. Часы, которые кто-то должен оплачивать, часы, которые не тратятся на разработку.

2. Использовать для этих целей IDE

На примере PhpStorm.

Плюсы

  • Автоматизация. Можно поправить код, просто набрав комбинацию клавиш.
  • Гибкость. Те, кто хоть раз настраивал стили кода в PhpStorm, знают сколько там есть правил для многих языков и как гибко их можно настраивать.

Минусы

  • Доступность. Во-первых, не у всех есть IDE. Тот же шторм, например, платный.
  • Доверие. Во-вторых, форматирование кода также остаётся на совести разработчика. Он может использовать свои правила или вообще не форматировать код.

3. Инструменты статического анализа


Плюсы

  • Автоматизация.
  • Доступность. Статический анализ можно запускать уже по хукам гита, причём даже не на компьютере разработчика, а на удалённом сервере.
  • Обязательность. Тот же подход помогает сделать правку и/или проверку кода обязательной для всех. Причём в едином стандарте компании/проекта.
  • Порог вхождения рядовых разработчиков. Отдельным разработчикам не нужно изучать никакие дополнительные инструменты, разворачивать системы. Всем этим занимается тимлид или кто-то другой компетентный.

Минусы

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

Из всех вариантов мы выбрали статические анализаторы. Их плюсы перевешивают для нас минусы, и они хорошо масштабируются.

Какие инструменты мы выбрали

Мы веб-студия и пишем бекенд на php, поэтому в первую очередь искали инструменты для него. Из всего многообразия мы отметили для себя три инструмента.

1. PHP CS Fixer

Инструмент для форматирования кода.

Плюсы

  • Помимо проверок может править форматирование кода в автоматическом режиме.
  • Можно настраивать под себя или использовать один из стандартов коробки (psr-2, symfony).

Минусы

  • Не всё можно настроить (сейчас).
  • Шторм всё-таки удобнее и гибче (хотя код библиотеки открыт, можно расширять своими силами).
  • Работает только с php файлами.
  • Иногда просто отказывается работать с файлом, говоря, что там ошибки и приходится методом тыка переписывать разные моменты в файле, чтобы фиксер не отказывался с ним работать.

Как на деле

Пример файла "до":
 <?
	function isHeCool($smn){
	if ($smn == 'love pineapple on pizza')
	{
	return false;
	}
	else
	{
	return true;
	}
	}
	$people = Array(
	0=>'love dogs',
	1=> 'pay for beer',
	2 => 'love pineapple on pizza',
	);
	$cool = 'cool';
	$notCool = 'not cool';
	foreach($people as $person){
	if(isHeCool($person)){
	echo "It's ".$cool." to ".$person;
	}
	}

Пример файла "после":
	<?php
	function isHeCool($smn) {
		if ($smn == 'love pineapple on pizza') {
			return false;
		} else {
			return true;
		}
	}
	$people = [
		0 => 'love dogs',
		1 => 'pay for beer',
		2 => 'love pineapple on pizza',
	];
	$cool = 'cool';
	$notCool = 'not cool';
	foreach ($people as $person) {
		if (isHeCool($person)) {
			echo "It's ".$cool." to ".$person;
		}
	}

2. PHP Copy Paste Detector

Инструмент для нахождения дублированных участков кода.

Плюсы

  • Отлично справляется со своей задачей.
  • И, кстати, от того же разработчика, что сделал phpunit, если кому-то важно.

Минусы

  • В Битриксе, из-за специфики хранения вёрстки в компонентах, бывают ложноположительные срабатывания.

Как на деле

Пример отчёта с одного из наших проектов

C:\Project>vendor\bin\phpcpd local

phpcpd 3.0.0 by Sebastian Bergmann.

Found 6 clones with 575 duplicated lines in 11 files:

- C:\Project\local\components\spectr\order.edit\class.php:177-206

C:\Project\local\components\spectr\order.info\class.php:43-72

- C:\Project\local\php_interface\lib\ComponentsParams.php:81-191

C:\Project\local\php_interface\lib\ComponentsParams.php:193-303

- C:\Project\local\php_interface\lib\ComponentsParams.php:828-907

C:\Project\local\php_interface\lib\ComponentsParams.php:1046-1125

- C:\Project\local\templates\main\components\bitrix\news.list\articles\template.php:2-66

C:\Project\local\templates\main\components\bitrix\news.list\news\template.php:2-66

C:\Project\local\templates\main\components\bitrix\news.list\rubber-tv\template.php:2-66

- C:\Project\local\templates\main\components\bitrix\sale.basket.basket.line\main\ajax_template.php:2-84

C:\Project\local\templates\main\components\bitrix\sale.basket.basket.line\personal-index\ajax_template.php:2-84

C:\Project\local\templates\main\components\bitrix\sale.basket.basket.line\success-add-order\ajax_template.php:2-84

- C:\Project\local\templates\main\components\bitrix\catalog.section\element-detail-similar-products\template.php:2-67

C:\Project\local\templates\main\components\bitrix\sale.recommended.products\main\template.php:2-67

5.76% duplicated lines out of 9979 total lines of code.



3. PHP Code Sniffer

То же самое, что и фиксер, но может не только в php.

Плюсы

  • Может работать и с другими типами файлов.

Минусы

  • Съедает очень много памяти. Так что даже не смогли потестить нормально.

Может быть, проверить на новом компе.

Внедрение в разработку

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

Установка инструментов

Ставить все инструменты мы будем через composer. Соответственно, вам нужно его предварительно поставить, чтобы двигаться дальше. Получить его можно здесь https://getcomposer.org/.

Не забудьте, что вам также нужен будет интерпретатор php в командной строке. Если у вас установлен, например, xampp, то эта проблема уже решена.

Ниже полный код нашего composer.json:
	{
		"require": {
			"sebastian/phpcpd": "^3.0",
			"squizlabs/php_codesniffer": "*",
			"friendsofphp/php-cs-fixer": "^2.4"
		},
		"scripts": {
			"pre-install-cmd": [
				"rm -rf .git/hooks",
				"ln -s local/git-hooks .git/hooks"
			],
			"post-install-cmd": [
				"rm -rf .git/hooks",
				"ln -s local/git-hooks .git/hooks"
			]
		}
	}

Пока не обращайте внимание на секцию scripts, мы вернёмся к ней позже.

Главное, что здесь нужно увидеть, что в блоке required мы запрашиваем все 3 инструмента, о которых мы говорили. Соответственно, чтобы скачать себе инструменты, нужно просто выполнить команду composer install в корне проекта.

Хуки

Мы будем запускать фиксер по событию коммита. Для этого нужно создать файл с названием pre-commit в папке .git/hooks со следующим кодом:
	#!/usr/bin/env bash
	# get the list of changed files
	staged_files=$(git diff --cached --name-only)
	# command to fix files
	cmd='vendor/bin/php-cs-fixer fix %s -q'
	if [ -f 'php_cs_fixer_rules.php' ]; then
		cmd='vendor/bin/php-cs-fixer fix %s -q --config=php_cs_fixer_rules.php'
	fi
	for staged in ${staged_files}; do #
		# work only with existing files
		if [[ -f ${staged} && ${staged} == *.php ]]; then #
			eval '$(printf "$cmd" "$staged")' # use php-cs-fixer to correct the file
			$(git add "$staged") # changes addition into the commit
		fi
	done
	exit 0 # do commit

Установка хуков

Хуки хранятся в .git/hooks, и эту папку нельзя включать под git. Поэтому мы идём на хитрость: закидываем код хуков, например, в local/git-hooks, а при установке проекта через композер копируем файлы в нужную папку.

Как раз об этом был блок scripts из composer.json (код выше).

Итог

Теперь каждый раз, когда программист будет коммитить код, этот код будет отформатирован в соответствии с правилами.

Настройка правил форматирования

У фиксера есть возможность создавать специальный файл, в котором прописываются правила. Подробнее о правилах вы можете прочитать здесь https://github.com/FriendsOfPHP/PHP-CS-Fixer .

Наш файл с правилами:
	<?php
	$finder = PhpCsFixer\Finder::create()
	->in(__DIR__);
	return PhpCsFixer\Config::create()
	->setIndent("\t")// использовать табы
	->setLineEnding("\r\n") // иначе могут быть проблемы с коммитами
	->setRules([
		'@PSR2' => true,
		'full_opening_tag' => true, // <? или <?php (здесь второй вариант)
		'braces' => [ // перенос скобочек
			'position_after_anonymous_constructs' => 'same',
			'position_after_control_structures' => 'same',
			'position_after_functions_and_oop_constructs' => 'same',
			'allow_single_line_closure' => true,
		],
		'object_operator_without_whitespace' => false, // это скобочки вокруг
		else и т.п.
		'array_syntax' => [
			'syntax' => 'short', // работа с массивом только через [], а не Array()
		],
		'no_extra_consecutive_blank_lines' => ['return'], // не очень помогает с
		лишними пустыми строками, но лучше чем extra, которое по умолчанию
		'binary_operator_spaces' => [
			'align_double_arrow' => false, // выравнивание массивов. true работает
			через раз, null не делает ничего, false ставит по одному пробелу вокруг
			"=>"
			'align_equals' => false, // выравнивание присвоений
		],
	])
	->setFinder($finder);

Что дальше

Дальше нужно, конечно же, нарабатывать правила для инструментов в соответствии со своим видением и стандартами компании. Возможно, дорабатывать инструменты, если правил не хватает. Код Fixer'а, например, открыт.

Плюс мы рассмотрели только автоматическое форматирование кода. Но в идеальном мире нам нужны более сложные и интеллектуальные вещи, например, проверки:

  • количества узлов в коде;
  • количества параметров в функции;
  • наличия else в коде (совсем уж идеальный мир).
Автор:
Аристов Василий
Разработчик Digital Spectr