sub foo ($$) { my ($a,$b) = @_; } foo(1,[3]); # no error
# hash-style arg passing sub foo { my (%args) = @_; my $fund = $args{fund} || $default; ... } ... foo(fund => $bla);
# key fund replaced by to_fund sub foo { my (%args) = @_; my $fund = $args{to_fund} || $default; ... } ... # and we forget to change here foo(fund => $bla);
NO ERROR!!
# a fund is a 6 digit integer... sub foo { confess "not a fund" if ($fund !~ /^[0-9]+$/); } sub bar { confess "not a fund" if ($fund !~ /^[0-9]{6}$/); } sub beer { confess "not a fund" if ($fund !~ /\d\d\d\d\d\d/); }
one day the type changes, a fund becomes 8 digits, and half of the regexps fail...
Mature code checks subroutine arguments heavily:
sub foo { my (%args) = @_; my $fund = delete $args{to_fund}; confess "unexpected args" if (keys %args); confess "to_fund undefined" if (!defined $fund); confess "to_fund is not a scalar" if (ref $fund ne ""); confess "not a valid fund" if (!is_fund $fund); ... }
# key fund replaced by to_fund sub foo { return (1,2,3); } # but you misread the (unexisting?) api documentation # for foo() and believe it returns a scalar: my $a = foo();
It runs but it's a bug.
The central idea of DbC is a metaphor on how elements of a software system collaborate with each other, on the basis of mutual obligations and benefits
Within DBC, every class/object/method/function must validate some:
Contracts even define failure strategies.
Contract implementations are limited with respect to:
put (x: ELEMENT; key: STRING) is require count <= capacity not key.empty do ... Some insertion algorithm ... ensure has (x) item (key) = x count = old count + 1 end
/** * @pre f >= 0.0 * @post Math.abs((return * return) - f) < 0.001 */ public float sqrt(float f) { ... }
package SomeClass; use Class::Agreement; invariant { my ($self) = @_; $self->count > 0; }; precondition add => sub { my ( $self, $value ) = @_; return ( $value >= 0 ); }; sub add { ... }
package ClassName use Class::Contract; contract { inherits 'BaseClass'; invar { ... }; method 'methodname'; pre { ... }; failmsg 'Error message'; post { ... }; failmsg 'Error message'; impl { ... }; # etc. };
use Sub::Contract qw(contract); # constraints are closures that return true or false contract('incr') ->in( sub { return defined $_[0] && $_[0] =~ /^\d+$/; } ) ->enable; sub incr { my $a = shift; return $a+1; }
use Sub::Contract qw(contract); sub is_integer { return defined $_[0] && $_[0] =~ /^\d+$/; } contract('incr') ->in(\&is_integer) ->out(\&is_integer) ->enable; sub incr { my $a = shift; return $a+1; }
use Sub::Contract qw(contract); contract('incr') ->in(\&is_integer) ->out(\&is_integer) ->enable; # incr(1) -> ok # incr("abc") -> fail # incr() -> fail # incr(1,2) -> fail # incr(1,undef) -> fail # incr([]) -> fail
package My::Constraints; sub is_integer { my $val = shift; return 0 if (!defined $val); return 0 if (ref $val ne ""); return ($val =~ /^\d+$/) ? 1:0; } sub is_longdate {...} sub is_shortdate {...} sub is_red_circle {...} sub is_loaned_booked {...} ...
use Sub::Contract qw(contract); use My::Constraints; contract('add') ->in( \&is_integer, undef, \&is_integer ) ->out( \&is_integer ) ->enable; sub add { my ($a,$b,$c) = @_; $b = 1 if (!defined $b); return $a+$b+$c; } # add(1,undef,3) -> ok # add(1,-6,3) -> fail
use Sub::Contract qw(contract); use My::Constraints; contract('add') ->in( a => \&is_integer, b => \&is_integer, c => \&is_integer )->enable; sub add { my (%args) = @_; return $args{a}+$args{b}+$args{c}; } # add(a => 1, b => 2, c => 3) -> ok # add(a => 1, b => 2) -> fail # add(a => 1, b => 2, c => undef) -> fail # add(a => 1, b => 2, c => 3, d => 4) -> fail
use Sub::Contract qw(contract); use My::Constraints; contract('add') ->in( \&is_integer, \&is_integer, c => \&is_integer ) ->enable; sub add { my ($a,$b,%args) = @_; return $a+$b+$args{c}; } # add(1,2,c => 3) -> ok # add(1,2,d => 3) -> fail # add(1,undef, c => 3) -> fail
use Sub::Contract qw(contract is_a); contract('foo') ->in( is_a("MyModule"), c => \&is_integer ) ->enable; sub foo {} # $object->foo(c => 123) -> ok # $object->foo() -> fail
use My::Constraints; use Sub::Contract qw(contract); # to_shortdate("2006-02-30 00:00:00") = "2006-02-30" sub to_shortdate { return substr($_[0],0,10); } contract('to_shortdate') ->in(\&is_longdate) ->out(\&is_shortdate) ->cache ->enable;
use Sub::Contract::Pool qw(get_contract_pool); my $pool = get_contract_pool; $pool->disable_all_contracts; # do some time-critical stuff $pool->enable_all_contracts;
use Sub::Contract::Pool qw(get_contract_pool); # disable all contracts contracting # subroutines under My::Module::* my $pool = get_contract_pool; my @c = $pool->find_contracts_matching("^My::Module::"); foreach my $contract (@c) { $contract->disable; }
# generate new contracts at runtime for unknown modules use Module::Pluggable search_path => [$path], require => 1; foreach my $module (__PACKAGE__->plugins()) { new Sub::Contract($module."::some_method") ->in(\&test1,\&test2) ->enable; }
use Sub::Contract qw( contract is_a defined_and undef_or ); contract('intersect') ->in(is_a("My::Line"), c => defined_and(is_a("My::Circle")), pos => undef_or(\&is_integer)) ->enable; # ex: # $line->intersect(c => $circle, pos => 12);