Динамический код:
модифицируем таблицу символов во время
выполнения
• Какие-то проблемы?
• Методы 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
Сторонние модули
print Dumper {test => 'Тестовая строка'};
www.mail.ru 10
Data::Dumper и
кириллица в utf8
$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}"
};
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
Решение: CPAN
• Необходимость доказательства
целесообразности
• Замусоривание системы
• Замусоривание блоков 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
Переопределение
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
eval
• Какие-то проблемы?
• Методы runtime-
кодогенерации
• Таблица символов:
матчасть
• От теории к практике
• Как не выстрелить себе
в ногу
• RTFM
18
• Таблица символов – это хэш
www.mail.ru 19
Таблица символов
• Ключи – глобальные переменные и
подпрограммы
• Значения - тайпглобы
%PackageName::
%main::
www.mail.ru 20
Таблица символов
package Test;
our $data = 'test';
our @data = qw(1 2 3);
our %data = (key1 => 'value1', key2 =>
'value2');
sub data { return 0; }
package main;
say $Test::{$_} for keys %Test::;
*Test::data
my $fh = *FH;
*glob{PACKAGE} имя пакета
*glob{NAME} имя элемента (переменной или функции)
*glob{SCALAR} ссылка на значение-скаляр
*glob{ARRAY} ссылка на значение-массив
*glob{HASH} ссылка на значение-хэш
*glob{CODE} ссылка на подпрограмму
corp.mail.ru
Структура тайпглоба
Получение данных
my $scalar = ${ *Test::data };
my %hash = %{ *Test::data };
my @array = @{ *Test::data };
&{ *Test::data }();
Запись данных
*Test::data = 'new value';
*Test::data = [4, 5, 6];
*Test::data = {
key3 => 'value3',
key4 => 'value4‘
};
*Test::data = sub { return 1; };
www.mail.ru 22
Работа с тайпглобом
• Какие-то проблемы?
• Методы runtime-
кодогенерации
• Таблица символов:
матчасть
• От теории к практике
• Как не выстрелить себе
в ногу
• RTFM
23
package MakeAccessor;
sub import {
my $package = caller(0);
no strict 'refs';
*{"$package::has"} = &has;
}
sub has ($) {
my $name = shift;
my $package = caller(0);
no strict 'refs';
*{"$package::$name"} = sub {
my $self = shift;
$self->{$name} = $_[0] if @_;
return $self->{$name};
};
}
www.mail.ru 24
Генерация аксессоров
package Test;
use MakeAccessor;
has 'name';
has 'age';
sub new { return bless
{}, shift; }
package main;
my $o = Test->new;
$o->name('Ann');
say $o->name;
package MakeAccessor;
sub import {
my $package = caller(0);
no strict 'refs';
*{"$package::has"} = &has;
}
sub has ($) {
my $name = shift;
my $package = caller(0);
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__
MakeAccessor::__ANON__
www.mail.ru 26
Боремся с __ANON__
package MakeAccessor;
sub import {
my $package = caller(0);
no strict 'refs';
*{"$package::has"} = &has;
}
sub has ($) {
my $name = shift;
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;
}
Test::name
• Генераторы классов: десериализация, ORM
• Реализация паттерна «прокси»
• Тестирование: mock, stub, fake object
• Хуки для подпрограмм: before, after, around
• Патчи во время выполнения
• Расширение функционала сторонних модулей
• Синонимы для устаревших функций при
рефакторинге
www.mail.ru 27
От теории к практике
package Response;
use CGI;
sub new {
return bless { cgi => CGI->new }, shift;
}
our $AUTOLOAD;
sub AUTOLOAD {
my ($method) = $AUTOLOAD =~ /([^:]+)$/;
return if $method eq 'DESTROY';
return unless CGI->can($method);
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 main;
my $obj = Response->new;
print $obj->header('text/html');
Content-Type:text/html;
charset=ISO-8859-1
package Wrapper;
sub make_deprecated {
my $deprecated = shift;
{
no strict 'refs';
return if *{$deprecated}{CODE};
};
my $package = scalar caller(0);
my $method = (caller(1))[3];
my $func = sub {
local *__ANON__ = $deprecated;
warn "$deprecated called at " . sprintf("%s
(%s)", (caller)[1, 2]) . " is deprecated. Use
$package::$methodn";
eval "use $package;" unless $package->can('can');
my $sub = $package->can($method);
$sub->(@_);
};
no strict 'refs';
*{$deprecated} = $func;
}
package Test1;
package Test2;
sub new_func {
Wrapper::make_deprecated(
'Test1::old_func');
return 'test';
}
package main;
say Test2::new_func();
say Test1::old_func();
www.mail.ru 29
Синонимы
test
Test1::old_func called at test12.pl
(40) is deprecated. Use
Test2::Test2::new_func
test
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
Stub
package Wrapper;
sub before(@&) {
my ($methods, $wrapper) = @_;
my $package = caller(0);
for my $method (@$methods) {
my $orig = $package->can($method);
my $sub = sub {
local *__ANON__ =
"$package::$method";
$wrapper->(@_);
$orig->(@_);
};
no strict 'refs';
*{"$package::$method"} = $sub;
}
}
package Test;
sub test { say 'test'; }
Wrapper::before [ 'test' ], sub { say 'wrapper';
};
package main;
Test::test();
www.mail.ru 31
Хуки
wrapper
test
• Какие-то проблемы?
• Методы 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

Динамический код: модифицируем таблицу символов во время выполнения. Елена Шишкина. Moscow.pm 4 апреля

  • 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.
    print Dumper {test=> 'Тестовая строка'}; www.mail.ru 10 Data::Dumper и кириллица в utf8 $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}" };
  • 11.
    package Foo; use Moose; hasfield1 => (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 Решение: CPAN
  • 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.
    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
  • 17.
    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 eval
  • 18.
    • Какие-то проблемы? •Методы runtime- кодогенерации • Таблица символов: матчасть • От теории к практике • Как не выстрелить себе в ногу • RTFM 18
  • 19.
    • Таблица символов– это хэш www.mail.ru 19 Таблица символов • Ключи – глобальные переменные и подпрограммы • Значения - тайпглобы %PackageName:: %main::
  • 20.
    www.mail.ru 20 Таблица символов packageTest; our $data = 'test'; our @data = qw(1 2 3); our %data = (key1 => 'value1', key2 => 'value2'); sub data { return 0; } package main; say $Test::{$_} for keys %Test::; *Test::data my $fh = *FH;
  • 21.
    *glob{PACKAGE} имя пакета *glob{NAME}имя элемента (переменной или функции) *glob{SCALAR} ссылка на значение-скаляр *glob{ARRAY} ссылка на значение-массив *glob{HASH} ссылка на значение-хэш *glob{CODE} ссылка на подпрограмму corp.mail.ru Структура тайпглоба
  • 22.
    Получение данных my $scalar= ${ *Test::data }; my %hash = %{ *Test::data }; my @array = @{ *Test::data }; &{ *Test::data }(); Запись данных *Test::data = 'new value'; *Test::data = [4, 5, 6]; *Test::data = { key3 => 'value3', key4 => 'value4‘ }; *Test::data = sub { return 1; }; www.mail.ru 22 Работа с тайпглобом
  • 23.
    • Какие-то проблемы? •Методы runtime- кодогенерации • Таблица символов: матчасть • От теории к практике • Как не выстрелить себе в ногу • RTFM 23
  • 24.
    package MakeAccessor; sub import{ my $package = caller(0); no strict 'refs'; *{"$package::has"} = &has; } sub has ($) { my $name = shift; my $package = caller(0); no strict 'refs'; *{"$package::$name"} = sub { my $self = shift; $self->{$name} = $_[0] if @_; return $self->{$name}; }; } www.mail.ru 24 Генерация аксессоров package Test; use MakeAccessor; has 'name'; has 'age'; sub new { return bless {}, shift; } package main; my $o = Test->new; $o->name('Ann'); say $o->name;
  • 25.
    package MakeAccessor; sub import{ my $package = caller(0); no strict 'refs'; *{"$package::has"} = &has; } sub has ($) { my $name = shift; my $package = caller(0); 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__ MakeAccessor::__ANON__
  • 26.
    www.mail.ru 26 Боремся с__ANON__ package MakeAccessor; sub import { my $package = caller(0); no strict 'refs'; *{"$package::has"} = &has; } sub has ($) { my $name = shift; 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; } Test::name
  • 27.
    • Генераторы классов:десериализация, ORM • Реализация паттерна «прокси» • Тестирование: mock, stub, fake object • Хуки для подпрограмм: before, after, around • Патчи во время выполнения • Расширение функционала сторонних модулей • Синонимы для устаревших функций при рефакторинге www.mail.ru 27 От теории к практике
  • 28.
    package Response; use CGI; subnew { return bless { cgi => CGI->new }, shift; } our $AUTOLOAD; sub AUTOLOAD { my ($method) = $AUTOLOAD =~ /([^:]+)$/; return if $method eq 'DESTROY'; return unless CGI->can($method); 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 main; my $obj = Response->new; print $obj->header('text/html'); Content-Type:text/html; charset=ISO-8859-1
  • 29.
    package Wrapper; sub make_deprecated{ my $deprecated = shift; { no strict 'refs'; return if *{$deprecated}{CODE}; }; my $package = scalar caller(0); my $method = (caller(1))[3]; my $func = sub { local *__ANON__ = $deprecated; warn "$deprecated called at " . sprintf("%s (%s)", (caller)[1, 2]) . " is deprecated. Use $package::$methodn"; eval "use $package;" unless $package->can('can'); my $sub = $package->can($method); $sub->(@_); }; no strict 'refs'; *{$deprecated} = $func; } package Test1; package Test2; sub new_func { Wrapper::make_deprecated( 'Test1::old_func'); return 'test'; } package main; say Test2::new_func(); say Test1::old_func(); www.mail.ru 29 Синонимы test Test1::old_func called at test12.pl (40) is deprecated. Use Test2::Test2::new_func test
  • 30.
    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 Stub
  • 31.
    package Wrapper; sub before(@&){ my ($methods, $wrapper) = @_; my $package = caller(0); for my $method (@$methods) { my $orig = $package->can($method); my $sub = sub { local *__ANON__ = "$package::$method"; $wrapper->(@_); $orig->(@_); }; no strict 'refs'; *{"$package::$method"} = $sub; } } package Test; sub test { say 'test'; } Wrapper::before [ 'test' ], sub { say 'wrapper'; }; package main; Test::test(); www.mail.ru 31 Хуки wrapper test
  • 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: Symboltables • perlref • perldata: Typeglobs and Filehandlers • Sriram Srinivasan.Advanced Perl Programming • Modern Perl (http://modernperlbooks.com) www.mail.ru 37 Что почитать
  • 38.