Выпуск 7. Сентябрь 2013

Пост-пост про YAPC::Europe 2013 в Киеве | Содержание | Сборка deb-пакетов модулей Perl для Debian и Ubuntu

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

Зачем?

С одной стороны, выразительность языка Perl позволяет писать код со скоростью мысли. С другой, запросто можно наставить пару-другую «подводных мин», которые проходят через все тесты (если они у вас есть) и тем не менее «взрываются», отображая errors и warnings, мало связанные с сутью проблемы.

А что, если заставить Perl заняться самоанализом?

Perl::Critic

Без сомнений, самый полный и самый важный инструмент из семьи статического анализа Perl. Утилита perlcritic исследует исходный код и оценивает его, следуя правилам из Perl Best Practices, помимо других, таких как, например, цикломатическая сложность.

Присутствуют элементы игрофикации: «уровни сложности» от 1 до 5 (называющиеся, соответственно: brutal, cruel, harsh, stern и gentle).

Например, вот результат запуска perlcritic -3 test.pl:

Code not contained in explicit package at line 1, column 1.  Violates encapsulation.  (Severity: 4)
Found use of Class::ISA. This module is deprecated by the Perl 5 Porters at line 4, column 1.  Find an alternative module.  (Severity: 5)
Found use of Switch. This module is deprecated by the Perl 5 Porters at line 5, column 1.  Find an alternative module.  (Severity: 5)
Module does not end with "1;" at line 5, column 1.  Must end with a recognizable true value.  (Severity: 4)

Важно понять, что Perl::Critic никому ничего не навязывает, так же как и не пропагандирует некий стандартный диалект «Modern Perl»! Его всегда можно запросто «успокоить»:

my $const_value = eval $const_name . '()'; ## no critic

Только на уровне harsh требуется доказать Perl::Critic, что понимаешь, что делаешь (при этом учитывается область видимости как у прагм):

my $const_value = eval {
    ## no critic (ProhibitNoStrict ProhibitNoWarnings)
    no strict qw(refs);
    no warnings qw(once);
    return *$const_name->();
};

Для личных настроек и исключений имеется файл ~/.perlcriticrc:

severity = harsh

[TestingAndDebugging::RequireUseWarnings]
equivalent_modules = common::sense

[TestingAndDebugging::RequireUseStrict]
equivalent_modules = common::sense

Совет: полезно ознакомится со списком policies, которые присутствуют в дефолтовым дистрибутиве Perl::Critic. Помимо них, имеется Perl::Critic::Pulp, сборник других полезных policies, например, Perl::Critic::Policy::Modules::ProhibitModuleShebang (запрет на использование хэшбэнга #!/usr/bin/perl в файлах типа .pm). Для использования этой policy достаточно добавить в ~/.perlcriticrc:

[Modules::ProhibitModuleShebang]
severity = gentle

При написании тестов для своих модулей можно воспользоваться Test::Perl::Critic (не забывая при этом проверять в таком тесте наличие $ENV{AUTHOR_TESTING}!).

Ну и, если лень устанавливать из CPAN, имеется в наличии webservice perlcritic.com, à lá validator.w3.org.

Perl::MinimumVersion

С кем не случалось: у тебя код работает, а у $whoever - нет? Иногда это всего лишь наличие $a //= 1 или map { s/^\s+|\s+$//gr } @b в одной единственной строке здоровенной программы. Утилита perlver помогает двухсторонне: она показывает, на какую версию Perl рассчитывал разработчик, а также место потенциальных «эксцессов» в коде. Например, вот результат запуска perlver --blame test.pl:

 ------------------------------------------------------------
 File    : test.pl
 Line    : 8
 Char    : 4
 Rule    : _perl_5010_operators
 Version : 5.010
 ------------------------------------------------------------
 //=
 ------------------------------------------------------------

Для своих тестов можно использовать Test::MinimumVersion (в этом случае более подходит ограничение на $ENV{RELEASE_TESTING}).

Кстати: иногда corelist служит интересным дополнением perlver. Например, чтобы узнать, с каких пор в Perl присутствует синтаксис given/when, запускаем corelist --feature switch:

Data for 2013-08-11
feature "switch" was first released with the perl v5.9.5 feature bundle

Perl::PrereqScanner

Еще один грешок - не оповещать свои dependencies (или оповещать, но не полностью). Dist::Zilla делает это автоматом и притом чересчур эффективно: запросто притесывается всякая чепуха, без которой легко можно обойтись (например, заменив Text::Trim на s/^\s+|\s+$//g). К счастью, можно сканировать и вручную, посредством scan_prereqs:

scan_prereqs --combine lib/ t/

Carp             = 0
Config           = 0
Fcntl            = 0
HTTP::Date       = 0
LWP::Protocol    = 0
LWP::UserAgent   = 0
Net::Curl::Easy  = 0
Net::Curl::Multi = 0
Net::Curl::Share = 0
Scalar::Util     = 0
base             = 0
strict           = 0
utf8             = 0
warnings         = 0

А Dist::Zilla можно научить «пропускать» только модули из стандартного (CORE) дистрибутива Perl при помощи Dist::Zilla::Plugin::OnlyCorePrereqs.

Test::Mojibake

scan_mojibake из этого дистрибутива помогает избегать проблем с кракозябрами, санкционируя употребление use utf8 / =encoding utf8:

not ok 13 - Mojibake test for t/bad/bad-latin1.pl_
#   Failed test 'Mojibake test for t/bad/bad-latin1.pl_'
#   at /Users/stas/perl5/perlbrew/perls/perl-5.16.2/lib/site_perl/5.16.2/Test/Mojibake.pm line 168.
# Non-UTF-8 unexpected in t/bad/bad-latin1.pl_, line 6 (source)
not ok 14 - Mojibake test for t/bad/bad-utf8.pl_
#   Failed test 'Mojibake test for t/bad/bad-utf8.pl_'
#   at /Users/stas/perl5/perlbrew/perls/perl-5.16.2/lib/site_perl/5.16.2/Test/Mojibake.pm line 168.
# UTF-8 unexpected in t/bad/bad-utf8.pl_, line 5 (source)

Test::Vars

Если GCC предупреждает о присутствии неиспользованных переменных, то что мешает Perl делать то же самое?! Только лишь отсутствие Test::Vars в стандартном дистрибутиве. Увы, у этого модуля нет утилиты stand-alone. Тем не менее, можно запускать его таким вот образом:

perl -MTest::Vars -e 'all_vars_ok()'

Test::Vars собирает имена анализируемых файлов из MANIFEST, поэтому если такого файла нет, фокус не удастся. Вот пример вывода:

# $result is used once in &LWP::Protocol::ftp::__ANON__[lib/LWP/Protocol/ftp.pm:307] at lib/LWP/Protocol/ftp.pm line 278
# $mtime is used once in &LWP::Protocol::ftp::request at lib/LWP/Protocol/ftp.pm line 357
# $mode is used once in &LWP::Protocol::ftp::request at lib/LWP/Protocol/ftp.pm line 357
not ok 14 - lib/LWP/Protocol/ftp.pm
#   Failed test 'lib/LWP/Protocol/ftp.pm'
#   at -e line 1.

Pod::Coverage

Неполная документация хуже отсутствующей документации. Поэтому Pod::Coverage сканирует определения функций и методов в исходном коде и проверяет наличие соответствующей документации POD. Утилита pod_cover также зависит от файла MANIFEST:

Summary:
 sub routines total    : 63
 sub routines covered  : 56
 sub routines uncovered: 7
 total coverage        : 88.88%

И, конечно же, имеется модуль Test::Pod::Coverage.

Все вместе

Созданный для настоящих Perl-программистов, Dist::Zilla::PluginBundle::TestingMania собирает некоторые из этих модулей (и многие другие).

Станислав Пусеп


Пост-пост про YAPC::Europe 2013 в Киеве | Содержание | Сборка deb-пакетов модулей Perl для Debian и Ubuntu
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье