Динамический код:
модифицируем таблицу символов во время
             выполнения
• Какие-то проблемы?
• Методы runtime-
  кодогенерации
• Таблица символов:
  матчасть
• От теории к практике
• Как не выстрелить себе
  в ногу
• RTFM

                           2
Повторяющийся код

• Конструкторы
• Методы-аксессоры
• Идентичная предварительная обработка
  данных
• Похожие по функционалу функции с
  небольшими отличиями




                  corp.mail.ru
Конструкторы

package Foo;
sub new {
    return bless {}, shift;
}


package Bar;
sub new {
    return bless {}, shift;
}


                   www.mail.ru    4
Аксессоры
sub field1 {
    my $self = shift;
    $self->{field1} = $_[0] if @_;
    return $self->{field1};
}

sub field2 {
    my $self = shift;
    $self->{field2} = $_[0] if @_;
    return $self->{field2};
}

sub field3 {
    my $self = shift;
    $self->{field3} = $_[0] if @_;
    return $self->{field3};
}




                                 www.mail.ru     5
Предварительная
                               обработка
sub do_something {
    my $self = shift;
    $self->check_cookies;
    return $self->redirect('/login') unless $self->check_auth;
    my $form = $self->load_form('do_something');
    $form->fetch;
    return $self->render_error() unless $form->validate;
    my $some_user_data = $self->load_user_data;
    ...
}

sub do_another_thing {
    my $self = shift;
    $self->check_cookies;
    ...
}




                                 www.mail.ru                     6
Похожие функции
sub error {
    my $message = shift;
    my ($package, $line, $sub) = (caller(0))[0, 2, 3];
    print $log scalar localtime, "ERROR: ${package}::$sub ($line): $messagen";
    print $log Carp::longmess if $Trace_Errors;
}

sub debug {
    my $message = shift;
    my ($package, $line, $sub) = (caller(0))[0, 2, 3];
    print $log scalar localtime, "DEBUG: ${package}::$sub ($line): $messagen";
}

sub info { ... }
sub warning { ... }




                                 www.mail.ru                                      7
Проблемы повторяющегося кода

• Потеря времени на
  перепечатывание/копирование
• Ошибки из-за невнимательности
• Трудоемкость сопровождения




                  corp.mail.ru
Сторонние модули

• Ошибки в реализации
• Отсутствие поддержки кириллицы
• Недостаточный функционал




                  corp.mail.ru
Data::Dumper и
                   кириллица в utf8
print Dumper {test => 'Тестовая строка'};


  $VAR1 = {
            "test" =>
  "x{422}x{435}x{441}x{442}x{43e}x{432}x{430}x
  {44f} x{441}x{442}x{440}x{43e}x{43a}x{430}"
          };




                     www.mail.ru                         10
Решение: CPAN

package Foo;
use Moose;


has field1 => (is => 'rw');
has field2 => (is => 'rw');
has field3 => (is => 'rw');


around [qw(do_something do_another_thing)] => sub {
     my ($orig, $self) = @_;
     ...
     $self->$orig(form => $form, user_data => $some_user_data);
};



                               www.mail.ru                        11
Проблемы использования
                сторонних модулей

• Необходимость доказательства
  целесообразности
• Замусоривание системы
• Замусоривание блоков use в коде
• Снижение производительности
• Увеличение времени компиляции
• Расход памяти
• Уменьшение контроля над кодом («чужой
  код»)
                www.mail.ru               12
• Какие-то проблемы?
• Методы runtime-
  кодогенерации
• Таблица символов:
  матчасть
• От теории к практике
• Как не выстрелить себе
  в ногу
• RTFM

                           13
Модификация кода

• Переопределение подпрограмм
• eval
• Изменение таблицы символов




                www.mail.ru     14
Переопределение

use Data::Dumper;


$Data::Dumper::Useqq = 1;
{
    no warnings 'redefine';
    package Data::Dumper;


    sub Data::Dumper::qquote {
        my $s = shift;
        return "'$s'";
    }
}



                              www.mail.ru   15
eval

package Wrapper;
sub make_accessors {
    my $package = caller(0);
    for (@_) {
        eval qq{
            package $package;
            sub $_ {
                my $self = shift;
                $self->{$_} = $_[0] if @_;
                return $self->{$_};
            }
        };
    }
}



                           www.mail.ru          16
eval

package Test;
use Wrapper;

sub new { return bless {}, shift; }

Wrapper::make_accessors( qw(name age) );

package main;
use Test;

my $obj = Test->new;
$obj->name('Ann');
say $obj->name;




                           www.mail.ru     17
• Какие-то проблемы?
• Методы runtime-
  кодогенерации
• Таблица символов:
  матчасть
• От теории к практике
• Как не выстрелить себе
  в ногу
• RTFM

                           18
Таблица символов

• Таблица символов – это хэш
  %PackageName::
  %main::
• Ключи – глобальные переменные и
  подпрограммы
• Значения - тайпглобы




                   www.mail.ru      19
Таблица символов

package Test;


our $data = 'test';                      *Test::data
our @data = qw(1 2 3);
our %data = (key1 => 'value1', key2 =>
'value2');

                                         my $fh = *FH;
sub data { return 0; }


package main;
say $Test::{$_} for keys %Test::;




                           www.mail.ru                    20
Структура тайпглоба




*glob{PACKAGE}         имя пакета
*glob{NAME}            имя элемента (переменной или функции)
*glob{SCALAR}          ссылка на значение-скаляр
*glob{ARRAY}           ссылка на значение-массив
*glob{HASH}            ссылка на значение-хэш
*glob{CODE}            ссылка на подпрограмму




                      corp.mail.ru
Работа с тайпглобом

Получение данных                      Запись данных
my $scalar = ${ *Test::data };        *Test::data = 'new value';
my %hash    = %{ *Test::data };       *Test::data = [4, 5, 6];
my @array   = @{ *Test::data };       *Test::data = {
&{ *Test::data }();                          key3 => 'value3',
                                             key4 => 'value4‘
                                      };
                                      *Test::data = sub { return 1; };




                            www.mail.ru                              22
• Какие-то проблемы?
• Методы runtime-
  кодогенерации
• Таблица символов:
  матчасть
• От теории к практике
• Как не выстрелить себе
  в ногу
• RTFM

                           23
Генерация аксессоров

package MakeAccessor;                             package Test;
sub import {                                      use MakeAccessor;
    my $package = caller(0);
    no strict 'refs';
                                                  has 'name';
    *{"$package::has"} = &has;
                                                  has 'age';
}


sub has ($) {                                     sub new { return bless {},
    my $name = shift;
                                                  shift; }
    my $package = caller(0);
    no strict 'refs';                             package main;
    *{"$package::$name"} = sub {
         my $self = shift;                        my $o = Test->new;
         $self->{$name} = $_[0] if @_;            $o->name('Ann');
         return $self->{$name};
                                                  say $o->name;
    };
}


                                    www.mail.ru                                24
Боремся с __ANON__
package MakeAccessor;
sub import {
    my $package = caller(0);
    no strict 'refs';
    *{"$package::has"} = &has;
}

sub has ($) {
    my $name = shift;
    my $package = caller(0);                    MakeAccessor::__ANON__
    no strict 'refs';
    *{"$package::$name"} = sub {
        my $self = shift;
        say ((caller(0))[3]);
        $self->{$name} = $_[0] if @_;
        return $self->{$name};
    };
}




                                        www.mail.ru                      25
Боремся с __ANON__

package MakeAccessor;

sub import {
    my $package = caller(0);
    no strict 'refs';
    *{"$package::has"} = &has;
}

sub has ($) {
    my $name = shift;
                                                    Test::name
    my $package = caller(0);
    my $method = sub {
        local *__ANON__ = "$package::$name";
        my $self = shift;
        $self->{$name} = $_[0] if @_;
        return $self->{$name};
    };
    no strict 'refs';
    *{"$package::$name"} = $method;
}




                                      www.mail.ru                26
От теории к практике

•   Генераторы классов: десериализация, ORM
•   Реализация паттерна «прокси»
•   Тестирование: mock, stub, fake object
•   Хуки для подпрограмм: before, after, around
•   Патчи во время выполнения
•   Расширение функционала сторонних модулей
•   Синонимы для устаревших функций при
    рефакторинге

                   www.mail.ru                27
Прокси
package Response;                                package main;
use CGI;                                         my $obj = Response->new;
sub new {
                                                 print $obj->header('text/html');
    return bless { cgi => CGI->new }, shift;
}
our $AUTOLOAD;
sub AUTOLOAD {
    my ($method) = $AUTOLOAD =~ /([^:]+)$/;
                                                 Content-Type: text/html;
    return if $method eq 'DESTROY';
    return unless CGI->can($method);             charset=ISO-8859-1
    my $sub = sub {
        local *__ANON__ = $AUTOLOAD;
        my $self = shift;
        return $self->{cgi}->$method(@_);
    };
    { no strict 'refs';
        *{$AUTOLOAD} = $sub;
    };
    return $sub->(@_);
}

                                   www.mail.ru                                      28
Синонимы

package Wrapper;                                                package Test1;

sub make_deprecated {                                           package Test2;
    my $deprecated = shift;
    {                                                           sub new_func {
        no strict 'refs';                                           Wrapper::make_deprecated(
        return if *{$deprecated}{CODE};                                 'Test1::old_func');
    };                                                              return 'test';
    my $package = scalar caller(0);                             }
    my $method = (caller(1))[3];
                                                                package main;
    my $func = sub {                                            say Test2::new_func();
        local *__ANON__ = $deprecated;                          say Test1::old_func();
        warn "$deprecated called at " . sprintf("%s (%s)",
(caller)[1, 2]) . " is deprecated. Use $package::$methodn";   test
        eval "use $package;" unless $package->can('can');
                                                                Test1::old_func called at test12.pl
        my $sub = $package->can($method);
                                                                (40) is deprecated. Use
        $sub->(@_);
                                                                Test2::Test2::new_func
    };
                                                                test
    no strict 'refs';
    *{$deprecated} = $func;
}



                                          www.mail.ru                                                 29
Stub

use Test::More;

my $user_data = { ... };
{
    no strict 'refs';
    *{'Cache::Memcached::set'} = sub {
        return 1;
    };
    *{'Cache::Memcached::get'} = sub {
        return to_json($user_data);
    };
};

is_deeply($user->get_info($session_id), $user_data, 'Some test...');



                           www.mail.ru                                 30
Хуки

package Wrapper;                                 package Test;

sub before(@&) {                                 sub test { say 'test'; }
    my ($methods, $wrapper) = @_;
    my $package = caller(0);                     Wrapper::before [ 'test' ], sub { say 'wrapper';
                                                 };
    for my $method (@$methods) {
        my $orig = $package->can($method);
                                                 package main;
        my $sub = sub {
                                                 Test::test();
            local *__ANON__ =
"$package::$method";
            $wrapper->(@_);
            $orig->(@_);
        };
        no strict 'refs';
        *{"$package::$method"} = $sub;          wrapper
    }                                            test
}




                                       www.mail.ru                                            31
• Какие-то проблемы?
• Методы runtime-
  кодогенерации
• Таблица символов:
  матчасть
• От теории к практике
• Как не выстрелить себе
  в ногу
• RTFM

                           32
Проблемы
               кодогенерации

• Прагмы strict и warnings
• __ANON__ в трассировке стэка
• Ссылки на переопределяемые функции
• Снижение читабельности кода и повышение
  требований к профессиональному уровню
  программистов
• Рост стэка при использовании хуков
• Пространство имен модуля

                www.mail.ru                 33
Ссылки

use Data::Dumper;


*{Data::Dumper::Dumper} = sub {
     return 'Hacked!';
};


say Dumper({key1 => 1, key2 => 2});
say Data::Dumper::Dumper({key1 => 1, key2 => 2});




                    www.mail.ru                     34
Пространство имен

package Wrapper;
our $name = 'Ann';

*{Test::test} = sub {
    say $name;
    say $Test::name;
};

package Test;
our $name = 'Bob';

package main;
Test::test();


                        www.mail.ru      35
eval                            Таблица символов
• Компиляция во время           • Компиляция при загрузке
  выполнения                    • Проверка синтаксиса
• Ускоренная загрузка,            компилятором при запуске
  возможность компиляции по     • Высокая эффективность
  запросу                         повторного использования
• Создание символов в             генераторов
  пространстве имен нужного     • Невозможность создания
  модуля                          символов в пространстве
• Неэффективная генерация         имен нужного модуля, если
  большого количества             имя последнего не известно
  похожих функций                 во время компиляции

                      www.mail.ru                          36
Что почитать

   • perlmod: Symbol tables
   • perlref
   • perldata: Typeglobs and
     Filehandlers
   • Sriram Srinivasan. Advanced
     Perl Programming
   • Modern Perl
     (http://modernperlbooks.com)


www.mail.ru                    37
Шишкина Елена Владимировна

      e.shishkina@corp.mail.ru

Perl: Symbol table

  • 1.
    Динамический код: модифицируем таблицусимволов во время выполнения
  • 2.
    • Какие-то проблемы? •Методы runtime- кодогенерации • Таблица символов: матчасть • От теории к практике • Как не выстрелить себе в ногу • RTFM 2
  • 3.
    Повторяющийся код • Конструкторы •Методы-аксессоры • Идентичная предварительная обработка данных • Похожие по функционалу функции с небольшими отличиями corp.mail.ru
  • 4.
    Конструкторы package Foo; sub new{ return bless {}, shift; } package Bar; sub new { return bless {}, shift; } www.mail.ru 4
  • 5.
    Аксессоры sub field1 { my $self = shift; $self->{field1} = $_[0] if @_; return $self->{field1}; } sub field2 { my $self = shift; $self->{field2} = $_[0] if @_; return $self->{field2}; } sub field3 { my $self = shift; $self->{field3} = $_[0] if @_; return $self->{field3}; } www.mail.ru 5
  • 6.
    Предварительная обработка sub do_something { my $self = shift; $self->check_cookies; return $self->redirect('/login') unless $self->check_auth; my $form = $self->load_form('do_something'); $form->fetch; return $self->render_error() unless $form->validate; my $some_user_data = $self->load_user_data; ... } sub do_another_thing { my $self = shift; $self->check_cookies; ... } www.mail.ru 6
  • 7.
    Похожие функции sub error{ my $message = shift; my ($package, $line, $sub) = (caller(0))[0, 2, 3]; print $log scalar localtime, "ERROR: ${package}::$sub ($line): $messagen"; print $log Carp::longmess if $Trace_Errors; } sub debug { my $message = shift; my ($package, $line, $sub) = (caller(0))[0, 2, 3]; print $log scalar localtime, "DEBUG: ${package}::$sub ($line): $messagen"; } sub info { ... } sub warning { ... } www.mail.ru 7
  • 8.
    Проблемы повторяющегося кода •Потеря времени на перепечатывание/копирование • Ошибки из-за невнимательности • Трудоемкость сопровождения corp.mail.ru
  • 9.
    Сторонние модули • Ошибкив реализации • Отсутствие поддержки кириллицы • Недостаточный функционал corp.mail.ru
  • 10.
    Data::Dumper и кириллица в utf8 print Dumper {test => 'Тестовая строка'}; $VAR1 = { "test" => "x{422}x{435}x{441}x{442}x{43e}x{432}x{430}x {44f} x{441}x{442}x{440}x{43e}x{43a}x{430}" }; www.mail.ru 10
  • 11.
    Решение: CPAN package Foo; useMoose; has field1 => (is => 'rw'); has field2 => (is => 'rw'); has field3 => (is => 'rw'); around [qw(do_something do_another_thing)] => sub { my ($orig, $self) = @_; ... $self->$orig(form => $form, user_data => $some_user_data); }; www.mail.ru 11
  • 12.
    Проблемы использования сторонних модулей • Необходимость доказательства целесообразности • Замусоривание системы • Замусоривание блоков use в коде • Снижение производительности • Увеличение времени компиляции • Расход памяти • Уменьшение контроля над кодом («чужой код») www.mail.ru 12
  • 13.
    • Какие-то проблемы? •Методы runtime- кодогенерации • Таблица символов: матчасть • От теории к практике • Как не выстрелить себе в ногу • RTFM 13
  • 14.
    Модификация кода • Переопределениеподпрограмм • eval • Изменение таблицы символов www.mail.ru 14
  • 15.
    Переопределение use Data::Dumper; $Data::Dumper::Useqq =1; { no warnings 'redefine'; package Data::Dumper; sub Data::Dumper::qquote { my $s = shift; return "'$s'"; } } www.mail.ru 15
  • 16.
    eval package Wrapper; sub make_accessors{ my $package = caller(0); for (@_) { eval qq{ package $package; sub $_ { my $self = shift; $self->{$_} = $_[0] if @_; return $self->{$_}; } }; } } www.mail.ru 16
  • 17.
    eval package Test; use Wrapper; subnew { return bless {}, shift; } Wrapper::make_accessors( qw(name age) ); package main; use Test; my $obj = Test->new; $obj->name('Ann'); say $obj->name; www.mail.ru 17
  • 18.
    • Какие-то проблемы? •Методы runtime- кодогенерации • Таблица символов: матчасть • От теории к практике • Как не выстрелить себе в ногу • RTFM 18
  • 19.
    Таблица символов • Таблицасимволов – это хэш %PackageName:: %main:: • Ключи – глобальные переменные и подпрограммы • Значения - тайпглобы www.mail.ru 19
  • 20.
    Таблица символов package Test; our$data = 'test'; *Test::data our @data = qw(1 2 3); our %data = (key1 => 'value1', key2 => 'value2'); my $fh = *FH; sub data { return 0; } package main; say $Test::{$_} for keys %Test::; www.mail.ru 20
  • 21.
    Структура тайпглоба *glob{PACKAGE} имя пакета *glob{NAME} имя элемента (переменной или функции) *glob{SCALAR} ссылка на значение-скаляр *glob{ARRAY} ссылка на значение-массив *glob{HASH} ссылка на значение-хэш *glob{CODE} ссылка на подпрограмму corp.mail.ru
  • 22.
    Работа с тайпглобом Получениеданных Запись данных my $scalar = ${ *Test::data }; *Test::data = 'new value'; my %hash = %{ *Test::data }; *Test::data = [4, 5, 6]; my @array = @{ *Test::data }; *Test::data = { &{ *Test::data }(); key3 => 'value3', key4 => 'value4‘ }; *Test::data = sub { return 1; }; www.mail.ru 22
  • 23.
    • Какие-то проблемы? •Методы runtime- кодогенерации • Таблица символов: матчасть • От теории к практике • Как не выстрелить себе в ногу • RTFM 23
  • 24.
    Генерация аксессоров package MakeAccessor; package Test; sub import { use MakeAccessor; my $package = caller(0); no strict 'refs'; has 'name'; *{"$package::has"} = &has; has 'age'; } sub has ($) { sub new { return bless {}, my $name = shift; shift; } my $package = caller(0); no strict 'refs'; package main; *{"$package::$name"} = sub { my $self = shift; my $o = Test->new; $self->{$name} = $_[0] if @_; $o->name('Ann'); return $self->{$name}; say $o->name; }; } www.mail.ru 24
  • 25.
    Боремся с __ANON__ packageMakeAccessor; sub import { my $package = caller(0); no strict 'refs'; *{"$package::has"} = &has; } sub has ($) { my $name = shift; my $package = caller(0); MakeAccessor::__ANON__ no strict 'refs'; *{"$package::$name"} = sub { my $self = shift; say ((caller(0))[3]); $self->{$name} = $_[0] if @_; return $self->{$name}; }; } www.mail.ru 25
  • 26.
    Боремся с __ANON__ packageMakeAccessor; sub import { my $package = caller(0); no strict 'refs'; *{"$package::has"} = &has; } sub has ($) { my $name = shift; Test::name my $package = caller(0); my $method = sub { local *__ANON__ = "$package::$name"; my $self = shift; $self->{$name} = $_[0] if @_; return $self->{$name}; }; no strict 'refs'; *{"$package::$name"} = $method; } www.mail.ru 26
  • 27.
    От теории кпрактике • Генераторы классов: десериализация, ORM • Реализация паттерна «прокси» • Тестирование: mock, stub, fake object • Хуки для подпрограмм: before, after, around • Патчи во время выполнения • Расширение функционала сторонних модулей • Синонимы для устаревших функций при рефакторинге www.mail.ru 27
  • 28.
    Прокси package Response; package main; use CGI; my $obj = Response->new; sub new { print $obj->header('text/html'); return bless { cgi => CGI->new }, shift; } our $AUTOLOAD; sub AUTOLOAD { my ($method) = $AUTOLOAD =~ /([^:]+)$/; Content-Type: text/html; return if $method eq 'DESTROY'; return unless CGI->can($method); charset=ISO-8859-1 my $sub = sub { local *__ANON__ = $AUTOLOAD; my $self = shift; return $self->{cgi}->$method(@_); }; { no strict 'refs'; *{$AUTOLOAD} = $sub; }; return $sub->(@_); } www.mail.ru 28
  • 29.
    Синонимы package Wrapper; package Test1; sub make_deprecated { package Test2; my $deprecated = shift; { sub new_func { no strict 'refs'; Wrapper::make_deprecated( return if *{$deprecated}{CODE}; 'Test1::old_func'); }; return 'test'; my $package = scalar caller(0); } my $method = (caller(1))[3]; package main; my $func = sub { say Test2::new_func(); local *__ANON__ = $deprecated; say Test1::old_func(); warn "$deprecated called at " . sprintf("%s (%s)", (caller)[1, 2]) . " is deprecated. Use $package::$methodn"; test eval "use $package;" unless $package->can('can'); Test1::old_func called at test12.pl my $sub = $package->can($method); (40) is deprecated. Use $sub->(@_); Test2::Test2::new_func }; test no strict 'refs'; *{$deprecated} = $func; } www.mail.ru 29
  • 30.
    Stub use Test::More; my $user_data= { ... }; { no strict 'refs'; *{'Cache::Memcached::set'} = sub { return 1; }; *{'Cache::Memcached::get'} = sub { return to_json($user_data); }; }; is_deeply($user->get_info($session_id), $user_data, 'Some test...'); www.mail.ru 30
  • 31.
    Хуки package Wrapper; package Test; sub before(@&) { sub test { say 'test'; } my ($methods, $wrapper) = @_; my $package = caller(0); Wrapper::before [ 'test' ], sub { say 'wrapper'; }; for my $method (@$methods) { my $orig = $package->can($method); package main; my $sub = sub { Test::test(); local *__ANON__ = "$package::$method"; $wrapper->(@_); $orig->(@_); }; no strict 'refs'; *{"$package::$method"} = $sub; wrapper } test } www.mail.ru 31
  • 32.
    • Какие-то проблемы? •Методы runtime- кодогенерации • Таблица символов: матчасть • От теории к практике • Как не выстрелить себе в ногу • RTFM 32
  • 33.
    Проблемы кодогенерации • Прагмы strict и warnings • __ANON__ в трассировке стэка • Ссылки на переопределяемые функции • Снижение читабельности кода и повышение требований к профессиональному уровню программистов • Рост стэка при использовании хуков • Пространство имен модуля www.mail.ru 33
  • 34.
    Ссылки use Data::Dumper; *{Data::Dumper::Dumper} =sub { return 'Hacked!'; }; say Dumper({key1 => 1, key2 => 2}); say Data::Dumper::Dumper({key1 => 1, key2 => 2}); www.mail.ru 34
  • 35.
    Пространство имен package Wrapper; our$name = 'Ann'; *{Test::test} = sub { say $name; say $Test::name; }; package Test; our $name = 'Bob'; package main; Test::test(); www.mail.ru 35
  • 36.
    eval Таблица символов • Компиляция во время • Компиляция при загрузке выполнения • Проверка синтаксиса • Ускоренная загрузка, компилятором при запуске возможность компиляции по • Высокая эффективность запросу повторного использования • Создание символов в генераторов пространстве имен нужного • Невозможность создания модуля символов в пространстве • Неэффективная генерация имен нужного модуля, если большого количества имя последнего не известно похожих функций во время компиляции www.mail.ru 36
  • 37.
    Что почитать • perlmod: Symbol tables • perlref • perldata: Typeglobs and Filehandlers • Sriram Srinivasan. Advanced Perl Programming • Modern Perl (http://modernperlbooks.com) www.mail.ru 37
  • 38.