Upload
elena-shishkina
View
469
Download
3
Embed Size (px)
DESCRIPTION
Слайды к докладу на конференции Moscow.pm 04.04.2013 Статья по мотивам доклада: http://blog.miralita.me/?p=81
Citation preview
Динамический код: модифицируем таблицу символов во время
выполнения
• Какие-то проблемы?
• Методы 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): $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 { ... }
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\::$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();
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
Что почитать
Шишкина Елена Владимировна