Стиль и эффективность

Качество имеет значение.

В программах есть ошибки. Программы требуют поддержки и расширения. Над программами работает множество программистов.

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

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

Написание поддерживаемого кода на Perl

Поддерживаемость — туманная мера простоты понимания и изменения существующей программы. Оставьте какой-нибудь код в стороне на шесть месяцев, а затем снова к вернитесь нему. Поддерживаемость определяет сложность, с котороый вы встретитесь при внесении изменений.

Поддерживаемость — не вопрос синтаксиса, как и не мера того, как ваш код будет выглядеть для непрограммиста. Представим компетентного программиста, понимающего суть задачи, которую код должен решать. С какими проблемами он встретится при корректном исправлении бага или добавлении улучшений?

Способность писать поддерживаемый код приходит из заработанного тяжёлым трудом опыта и поддерживается умелым обращением с идиомами, техниками и преобладающим стилем языка. Но даже новички могут улучшить поддерживаемость своего кода, следуя нескольким принципам:

Написание идиоматического кода на Perl

Perl свободно заимствует у других языков. Perl позволяет вам писать код так, как вы хотите его писать. Программисты на C зачастую пишут на Perl в стиле C, также как Java-программисты пишут на Perl в стиле Java. Эффективные Perl-программисты пишут на Perl в стиле Perl, включая применение идиом языка.

Написание эффективного кода на Perl

Поддерживаемость — в конечном счёте, вопрос дизайна. Хороший дизайн приходит с практикой полезных привычек:

Исключения

Программировать хорошо означает предвидеть неожиданное. Файлы, которые должны существовать, не существуют. Этот огромный диск, который никогда не заполнится, оказывается полным. Всегда доступная сеть не доступна. Неломающиеся базы данных ломаются. Исключения случаются, и надёжное программное обеспечение должно их обрабатывать. Если вы можете восстановиться, отлично! Если не можете, запишите нужную информацию в лог и повторите попытку.

Pelr 5 обрабатывает исключительные ситуации с помощью исключений: механизм потока управления динамической области видимости, разработанный для генерации и обработки ошибок.

Выбрасывание исключений

Предположим, вы хотите записать в лог файл. Если вы не смогли открыть файл, что-то пошло не так. Используйте die чтобы выбросить исключение:

    sub open_log_file
    {
        my $name = shift;
        open my $fh, '>>', $name
            or die "Can't open logging file '$name': $!";
        return $fh;
    }

die() устанавливает значение своего операнда в глобальную переменную $@ и немедленно выходит из текущей функции, ничего не возвращая. Это выброшенное исключение будет подниматься по стеку вызовов (Контроллируемое выполнение) до тех пор, пока не будет где-нибудь поймано. Если исключение не будет поймано нигде, программа завершится с ошибкой.

Обработка исключений использует ту же самую динамическую область видимости (Динамическая область видимости.), что и символы, объявленные с помощью local.

Отлавливание исключений

Иногда исключение, приводящее к выходу из программы, удобно. Программа, выполняемая как синхронизированный процесс, может выбросить исключение, если логи ошибок заполнены, что приведёт к отправке SMS администраторам. Однако, не все исключения должны быть фатальными. От некоторых из них хорошие программы должны уметь восстанавливаться, или по крайней мере сохранять текущее состояние и завершаться чисто.

Используйте блочную форму оператора eval чтобы поймать исключение:

    # лог-файл может не открыться
    my $fh = eval { open_log_file( 'monkeytown.log' ) };

Если файл успешно открыт, $fh будет содержать дескриптор файла. Если нет, $fh останется неопределённой и выполнение программы продолжится.

Блочный аргумента eval вводит новую область видимости, и лексическую, и динамическую. Если open_log_file() вызывает другие функции, и какая-нибудь из них в конечном счёте выбросила исключение, этот eval его поймает.

Обработка исключений — грубый инструмент. Он будет ловить все исключения в своей динамической области видимости. Чтобы проверить, какое исключение вы поймали (и поймали ли вообще), проверьте значение $@. Убедитесь, что локализовали $@ с помощью local, прежде чем попытаться поймать исключение; помните, что $@ — глобальная переменная:

    local $@;

    # лог-файл может не открыться
    my $fh = eval { open_log_file( 'monkeytown.log' ) };

    # поймано исключение
    if (my $exception = $@) { ... }

Сразу же скопируйте $@ в лексическую переменную, чтобы избежать возможности того, что последующий код перезапишет глобальную переменную $@. Вы никогда не знаете, что ещё могло использовать блок eval в каком-нибудь другом месте и сбросить $@.

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

    if (my $exception = $@)
    {
        die $exception
            unless $exception =~ /^Can't open logging/;
        $fh = log_to_syslog();
    }

Пробросьте исключение, снова вызвав die(). Передайте существующее исключение или новое, в зависимости от необходимости.

Применение к строковым исключениям регулярных выражений может быть ненадёжным, потому что сообщения об ошибках могут со временем меняться. Это относится и к внутренним исключениям, которые выбрасывает сам Perl. К счастью, вы можете также передавать в die ссылку — даже благословлённую ссылку. Это позволяет вам предоставлять в вашем исключении гораздо больше информации: номера строк, файлы и другую отладочную информацию. Получение этой информации из чего-то структурированного намного легче, чем разбор строки. Ловите эти исключения так же, как любые другие.

CPAN-дистрибутив Exception::Class упрощает создание и использование объектов-исключений:

    package Zoo::Exceptions
    {
        use Exception::Class
            'Zoo::AnimalEscaped',
            'Zoo::HandlerEscaped';
    }

    sub cage_open
    {
        my $self = shift;
        Zoo::AnimalEscaped->throw
            unless $self->contains_animal;
        ...
    }

    sub breakroom_open
    {
        my $self = shift;
        Zoo::HandlerEscaped->throw
            unless $self->contains_handler;
        ...
    }

Предостережения об исключениях

Хотя выбрасывание исключений относительно просто, ловить их сложнее. Корректное использование $@ требует от вас обходить некоторые неочевидные риски:

Perl 5.14 исправил некоторые из этих проблем. Признаем, они встречаются очень редко, но зачастую сложны для диагностики и исправления. CPAN-дистрибутив Try::Tiny улучшает безопасность обработки исключений и синтаксис (footnote: На самом деле, Try::Tiny помог вдохновить на улучшения в обработке исключений в Perl 5.14.).

Try::Tiny прост в использовании:

    use Try::Tiny;

    my $fh = try   { open_log_file( 'monkeytown.log' ) }
             catch { log_exception( $_ ) };

try заменяет eval. Необязательный блок catch выполняется, только если try поймал исключение. catch получает пойманное исключение как переменную-топик $_.

Встроенные исключения

Сам Perl 5 выбрасывает несколько исключительных условий. perldoc perldiag перечисляет несколько «отлавливаемых фатальных ошибок». Тогда как часть из них — синтаксические ошибки, выбрасываемые в процессе компиляции, другие вы можете поймать во время выполнения. Наиболее интересны следующие:

Конечно, вы также можете ловить исключения, производимые autodie (Прагма autodie), и любые лексические предупреждения, повышенные до исключений (Регистрация своих собственных предупреждений).

Прагмы

Большинство расширений Perl 5 — модули, предоставляющие новые функции или определяющие классы (Moose). Некоторые же модули вместо этого влияют на поведение самого языка, как strict или warnings. Такие модули называют прагмами. По соглашению прагмы имеют имена в нижнем регистре, чтобы отличить их от других модулей.

Прагмы и область видимости

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

    {
        # $lexical невидима; strict не действует
        {
            use strict;
            my $lexical = 'available here';
            # $lexical видима; strict действует
            ...
        }
        # $lexical снова невидима; strict не действует
    }

Также как лексические объявления воздействуют на внутренние области видимости, прагмы сохраняют свой эффект во внутренних областях видимости:

    # область видимости файла
    use strict;

    {
        # внутренняя область видимости, но strict всё ещё действует
        my $inner = 'another lexical';
        ...
    }

Использование прагм

Подключайте прагмы с помощью use, как и любой другой модуль. Прагмы принимают аргументы, такие как минимальный номер используемой версии и список аргументов для изменения поведения прагмы:

    # требует объявления переменных, запрещает голые слова
    use strict qw( subs vars );

Иногда вам нужно отключить все или часть этих эффектов в глубже вложенной лексической области видимости. Встроенная директива no отменяет импорт (Импорт), что отменяет эффекты правильно работающих прагм. Например, так можно отключить защиту strict, если вам нужно сделать что-нибудь символьное:

    use Modern::Perl;
    # или use strict;

    {
        no strict 'refs';
        # здесь можно манипулировать символьной таблицей
    }

Полезные прагмы

Perl 5.10.0 добавил возможность писать свои собственные лексические прагмы в виде кода на чистом Perl. perldoc perlpragma объясняет как это делать, а описание $^H в perldoc perlvar объясняет, как эта возможность работает.

Но и до 5.10 Perl 5 включал несколько полезных встроенных прагм.

CPAN начал собирать невстроенные прагмы:

Эти инструменты пока не имеют широкого использования. Два последних могут помочь вам писать более корректный код, тогда как с двумя предыдущими стоит поэкспериментировать в небольших проектах. Они показывают, чем мог бы быть Perl 5.