lancew@cpan.org
Lance Wicks,
Development team leader at www.cv-library.co.uk
100,000+ jobs from 10,000+ companies, 10,000,000+ users, 2,000,000+ job applications per month.
Perl 5 code base started in 2000.
http://www.cv-library.co.uk/yapceu
16 year old code base, Perl5, fast growth, lots of new features, 400,000 lines of code.
Refactoring is essential, but difficult.
Zero tolerance to breaking the site, or negatively impacting performance.
If you can't test it, don't refactor it.
EXODIST
use Test::More;
my $transactions_via_old = $candidate->transactions;
my $transactions_via_new = $candidate->transactions_new;
is_deeply(
$transactions_via_old,
$transactions_via_new,
'Old and new methods return same data structures'
);
done_testing;
If you can't measure it, don't refactor it.
COSIMO
use Time::HiRes;
my $start_time = [ Time::HiRes::gettimeofday ];
for my $candidate (@active_candidates) {
# Expensive transaction in loop of large data set
update_with_new_total($candidate);
Net::Statsd::inc('candidates.updated_with_new_totals');
}
Net::Statsd::timing(
'candidates.new_totals',
Time::HiRes::tv_interval($start_time) * 1000
);
If you can't turn it on AND off, don't refactor it.
CVLIBRARY
use Toggle;
use Redis;
my $redis = Redis->new;
my $toggle = Toggle->new( storage => $redis );
$toggle->define_group( staff => sub {
my $user = shift;
return $user->has_role('staff');
});
if ( $toggle->is_active(test_new_query => $user) ) {
$db_repo->get_transactions(user => $user)
}
Bringing it all together.
LANCEW
# TODO: Lets talk about that later. :-)
Unit, integration/service and end to end tests all run by Jenkins on every commit.
use CVLibrary::SalaryConverter 'convert_salary';
use Test::Fatal;
use Test::More;
sub is_salary_converted_from_day_month : Test {
is convert_salary( from => 'day', to => 'month', value => 75 ), 1629;
}
sub is_salary_converted_from_month_to_year : Test {
is convert_salary( from => 'month', to => 'annum', value => 1629 ), 19548;
}
sub is_salary_converted_from_year_to_week : Test {
is convert_salary( from => 'annum', to => 'month', value => 19500 ), 1625;
}
sub will_class_throw_if_from_param_invalid : Tests {
ok exception {
convert_salary( from => 'year', to => 'day', value => 22000 );
};
}
sub will_class_throw_if_to_param_invalid : Tests {
ok exception {
convert_salary( from => 'annum', to => 'monthly', value => 22000 );
};
}
package S2z8N3;{
$zyp=S2z8N3;use Socket;
(S2z8N3+w1HC$zyp)&
open SZzBN3,"<$0"
;while(<SZzBN3>){/\s\((.*p\))&/
&&(@S2zBN3=unpack$age,$1)}foreach
$zyp(@S2zBN3)
while($S2z8M3++!=$zyp-
30){$_=<SZz8N3>}/^(.)/|print $1
;$S2z8M3=0}s/.*//|print}sub w1HC{$age=c17
;socket(SZz8N3,PF_INET,SOCK_STREAM,getprotobyname('tcp'))&&
connect(SZz8N3,sockaddr_in(023,"\022\x17\x\cv"))
;S2zBN3|pack$age}
use Test::More;
use S2z8N3;
... ????
First place, 1st Annual Obfuscated Perl Contest: Joe Futrelle.
Net::Statsd & Graphite starting in 2013.
Net::Statsd::increment('site.logins');
use Time::HiRes;
my $start_time = [ Time::HiRes::gettimeofday ];
my $candidate_applications = $self->db->get_candidate_applications();
# note: time value sent to timing should
# be in milliseconds.
Net::Statsd::timing(
'candidate.applications',
Time::HiRes::tv_interval($start_time) * 1000
);
Net::Statsd::gauge('core.temperature' => 55);
Net::Statsd -> Graphite.
Net::Statsd -> Graphite.
Net::Statsd -> Graphite.
Toggle.
Toggle
if ( $toggle->is_enabled('chat') ) {
# Code for cool new chat feature
}
if ( $toggle->is_enabled('chat' => $user) ) {
# Code for cool new chat feature some users
}
Toggle
$toggle->activate_percentage( chat => 100 );
if ( $toggle->is_enabled('chat') ) {
# Code for cool new chat feature
}
$toggle->define_group( admins => sub { shift->is_an_admin() } );
if ( $toggle->is_enabled('chat' => $user) ) {
# Code for cool new chat feature
}
Toggle
if ( $toggle->is_enabled('new_candidate_applications') ) {
my $candidate_applications = $self->db->get_applications_new();
}
else
{
my $candidate_applications = $self->db->get_applications();
}
use Time::HiRes;
my $start_time = [ Time::HiRes::gettimeofday ];
if ( $toggle->is_enabled('new_candidate_applications') ) {
my $candidate_applications = $self->db->get_applications_new();
Net::Statsd::timing(
'candidate.applications.new',
Time::HiRes::tv_interval($start_time) * 1000
);
}
else
{
my $candidate_applications = $self->db->get_applications();
Net::Statsd::timing(
'candidate.applications.old',
Time::HiRes::tv_interval($start_time) * 1000
);
}
use Test::More;
use Time::HiRes;
my $start_time = [ Time::HiRes::gettimeofday ];
my $candidate_applications_new = $self->db->get_applications_new();
Net::Statsd::timing(
'candidate.applications.new',
Time::HiRes::tv_interval($start_time) * 1000
);
$start_time = [ Time::HiRes::gettimeofday ];
my $candidate_applications_old = $self->db->get_applications();
Net::Statsd::timing(
'candidate.applications.old',
Time::HiRes::tv_interval($start_time) * 1000
);
warn is_deeply($candidate_applications_old, $candidate_applications_new);
my $candidate_applications = $canidate_applications_old;
use Test::Deep::NoTest;
use Time::HiRes;
my $start_time = [ Time::HiRes::gettimeofday ];
my $candidate_applications_new = $self->db->get_applications_new();
Net::Statsd::timing(
'candidate.applications.new',
Time::HiRes::tv_interval($start_time) * 1000
);
$start_time = [ Time::HiRes::gettimeofday ];
my $candidate_applications_old = $self->db->get_applications();
Net::Statsd::timing(
'candidate.applications.old',
Time::HiRes::tv_interval($start_time) * 1000
);
if (eq_deeply($candidate_applications_new,
$candidate_applictaions_old)) {
Net::Statsd::increment('candidate.applications.ok');
}
my $candidate_applications = $candidate_applications_old;
use Scientist;
my $experiment = Scientist->new(
experiment => 'MyTest',
use => \&old_code,
try => \&new_code,
);
my $answer = $experiment->run;
warn 'There was a mismatch between control and candidate'
if $experiment->result->{'mismatched'};
use Scientist;
my $experiment = Scientist->new(
experiment => 'candidate.applications',
use => \&old_code,
try => \&new_code,
);
my $candidate_applications = $experiment->run;
warn 'There was a mismatch between control and candidate'
if $experiment->result->{'mismatched'};
say 'Timings:';
say '"Use" code: ', $experiment->result->{control}{duration}, ' microseconds';
say '"Try" code: ', $experiment->result->{candidate}{duration}, ' microseconds';
package My::Scientist;
use parent 'Scientist';
use Net::Statsd;
sub publish {
my $self = shift;
my $experiment = $self->result->{experiment};
# Increment counter for every match or mismatch
Net::Statsd::increment("$experiment.mismatch") if $self->result->{mismatched};
Net::Statsd::increment("$experiment.match") unless $self->result->{mismatched};
# Log timings (converting Scientist microseconds to StatsD miliseconds)
# Note: This implementation rounds down the duration.
Net::Statsd::timing("$experiment.control",
int $self->result->{control}{duration} * 1_000);
Net::Statsd::timing("$experiment.candidate",
int $self->result->{candidate}{duration} * 1_000);
}
1;
use Test::Deep::NoTest;
use Time::HiRes;
my $start_time = [ Time::HiRes::gettimeofday ];
my $candidate_applications_new = $self->db->get_applications_new();
Net::Statsd::timing(
'candidate.applications.new',
Time::HiRes::tv_interval($start_time) * 1000
);
$start_time = [ Time::HiRes::gettimeofday ];
my $candidate_applications_old = $self->db->get_applications();
Net::Statsd::timing(
'candidate.applications.old',
Time::HiRes::tv_interval($start_time) * 1000
);
if (eq_deeply($candidate_applications_new,
$candidate_applictaions_old)) {
Net::Statsd::increment('candidate.applications.ok');
}
my $candidate_applications = $candidate_applications_old;
use My::Scientist;
my $experiment = My::Scientist->new(
experiment => 'candidate.applications',
use => \&get_applications(),
try => \&get_applications_new(),
);
my $candidate_applications = $experiment->run;
use My::Scientist;
my $experiment = My::Scientist->new(
enabled => $toggle->is_active('candidate.applications'),
experiment => 'candidate.applications',
use => \&get_applications(),
try => \&get_applications_new(),
);
my $candidate_applications = $experiment->run;
use My::Scientist;
my $experiment = My::Scientist->new(
experiment => 'candidate.applications',
use => \&get_applications(),
try => \&get_applications_new(),
);
my $candidate_applictaions = $experiment->run;
lancew@cpan.org
Thank you!
Any Questions?
lancew@cpan.org