Динамический код: модифицируем таблицу символов во...
Post on 11-Jun-2015
6.187 Views
Preview:
DESCRIPTION
TRANSCRIPT
Динамический код:модифицируем таблицу символов во время
выполнения
• Какие-то проблемы?• Методы runtime-
кодогенерации• Таблица символов:
матчасть• От теории к практике• Как не выстрелить себе
в ногу• RTFM
2
• Конструкторы• Методы-аксессоры• Идентичная предварительная обработка
данных• Похожие по функционалу функции с
небольшими отличиями
corp.mail.ru
Повторяющийся код
www.mail.ru 4
package Foo;
sub new {
return bless {}, shift;
}
package Bar;
sub new {
return bless {}, shift;
}
Конструкторы
www.mail.ru 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 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 7
sub error {
my $message = shift;
my ($package, $line, $sub) = (caller(0))[0, 2, 3];
print $log scalar localtime, "ERROR: ${package}::$sub ($line): $message\n";
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): $message\n";
}
sub info { ... }
sub warning { ... }
Похожие функции
• Потеря времени на перепечатывание/копирование
• Ошибки из-за невнимательности• Трудоемкость сопровождения
corp.mail.ru
Проблемы повторяющегося кода
• Ошибки в реализации• Отсутствие поддержки кириллицы• Недостаточный функционал
corp.mail.ru
Сторонние модули
www.mail.ru 10
print Dumper {test => 'Тестовая строка'};
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}" };
www.mail.ru 11
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);
};
Решение: CPAN
www.mail.ru 12
• Необходимость доказательства целесообразности
• Замусоривание системы• Замусоривание блоков use в коде• Снижение производительности• Увеличение времени компиляции• Расход памяти• Уменьшение контроля над кодом («чужой
код»)
Проблемы использования сторонних модулей
• Какие-то проблемы?• Методы runtime-
кодогенерации• Таблица символов:
матчасть• От теории к практике• Как не выстрелить себе
в ногу• RTFM
13
www.mail.ru 14
• Переопределение подпрограмм• eval• Изменение таблицы символов
Модификация кода
www.mail.ru 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 16
package Wrapper;sub make_accessors { my $package = caller(0); for (@_) { eval qq{ package $package; sub $_ { my \$self = shift; \$self->{$_} = \$_[0] if \@_; return \$self->{$_}; } }; }}
eval
www.mail.ru 17
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;
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
Структура тайпглоба
www.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; };
Работа с тайпглобом
• Какие-то проблемы?• Методы runtime-
кодогенерации• Таблица символов:
матчасть• От теории к практике• Как не выстрелить себе
в ногу• RTFM
23
www.mail.ru 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};
};
}
Генерация аксессоров
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;
www.mail.ru 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};
};
}
Боремся с __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
www.mail.ru 27
• Генераторы классов: десериализация, ORM• Реализация паттерна «прокси»• Тестирование: mock, stub, fake object• Хуки для подпрограмм: before, after, around• Патчи во время выполнения• Расширение функционала сторонних модулей• Синонимы для устаревших функций при
рефакторинге
От теории к практике
www.mail.ru 28
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->(@_);}
Прокси
package main;
my $obj = Response->new;
print $obj->header('text/html');
Content-Type: text/html; charset=ISO-8859-1
www.mail.ru 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\::$method\n"; 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();
Синонимы
testTest1::old_func called at test12.pl (40) is deprecated. Use Test2::Test2::new_functest
www.mail.ru 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...');
Stub
www.mail.ru 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();
Хуки
wrappertest
• Какие-то проблемы?• Методы runtime-
кодогенерации• Таблица символов:
матчасть• От теории к практике• Как не выстрелить себе
в ногу• RTFM
32
www.mail.ru 33
• Прагмы strict и warnings• __ANON__ в трассировке стэка• Ссылки на переопределяемые функции• Снижение читабельности кода и повышение
требований к профессиональному уровню программистов
• Рост стэка при использовании хуков• Пространство имен модуля
Проблемы кодогенерации
www.mail.ru 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 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 36
eval
• Компиляция во время выполнения
• Ускоренная загрузка, возможность компиляции по запросу
• Создание символов в пространстве имен нужного модуля
• Неэффективная генерация большого количества похожих функций
Таблица символов
• Компиляция при загрузке• Проверка синтаксиса
компилятором при запуске• Высокая эффективность
повторного использования генераторов
• Невозможность создания символов в пространстве имен нужного модуля, если имя последнего не известно во время компиляции
www.mail.ru 37
• perlmod: Symbol tables• perlref• perldata: Typeglobs and
Filehandlers• Sriram Srinivasan. Advanced
Perl Programming• Modern Perl
(http://modernperlbooks.com)
Что почитать
Шишкина Елена Владимировна
e.shishkina@corp.mail.ru
top related