Re: An example of programming by contract
by chromatic (Archbishop) on Nov 26, 2001 at 00:23 UTC
|
This would seem to be a good chance to use exceptions.
Stylistically, I'd probably use sub refs instead of the string eval, too. If your pre- and post- conditions are in a package, UNIVERSAL::can comes to the rescue. Otherwise, this is an opportunity for a little symbol table muckery:
sub validate {
my $input = shift;
my %arg = @_;
for (@{$arg{as}}) {
my $func = "validate_$_";
my $package = __PACKAGE__ . '::';
no strict 'refs';
if (exists ${ $package }{$func}) {
$func = *{ ${ $package }{$func} }{CODE};
return unless $func->($input);
} else {
# throw exception and/or return
}
}
warn "validated $input";
return 1;
}
Or you could build a hashref of validators, or cache them instead of explicitly going to the symbol table, or use the Listener pattern, or do all sorts of things. | [reply] [Watch: Dir/Any] [d/l] |
Re (tilly) 1: An example of programming by contract
by tilly (Archbishop) on Nov 26, 2001 at 01:36 UTC
|
As pointed out by chromatic, error conditions are
generally best handled by throwing exceptions, and not
by returning codes and assuming that your calling code
is going to always write the necessary wrapper to notice
the error conditions.
As an example of the programmer errors that the latter
approach lends itself to, look in your own code to your
use of string evals without any sanity checks for
whether $@ is being set. As a general rule string evals
are to be avoided as long as you have any other sane (or
saner) alternatives. That is not to say that they are bad,
just that they are very undisciplined as control
structures. Furthermore using manually synchronized
naming schemes as a control pattern lends itself to its
own problems. You can use a manually syncrhonized naming
pattern to do the same things you can do with real
anonymous data structures. But the latter is much
faster, more efficient, and saner to work with.
So to answer your second question, this is not the kind of
code that I would like to see in production. And the
main thing that I would cite is that it is trying to be
too clever while ignoring basic principles of sane
coding. Make sure that you have checked error conditions
and will get debuggable information if things go wrong.
Use real data structures where appropriate. If there exists
a language feature (eg die/eval pairs) to do what you
want, use it.
Sorry. | [reply] [Watch: Dir/Any] |
Re: An example of programming by contract
by premchai21 (Curate) on Nov 25, 2001 at 22:44 UTC
|
In perlvar, it is seen, $! is $OS_ERROR, that is, an error from the OS. I presume that your program is not the OS... you can't set $! AFAIK. I presume that you want to have error messages other than the standard ones from the OS, which you can't get from $! AFAIK. There is a way to set $@ to whatever you wish, though: by dieing. Other than that, I see no other way to signal errors, other than maybe making the return value an object which stringifies and numifies to itself, but which boolifies to false... perhaps using "Falsify" scalar strings?
Update: Thanks tilly. Apparently my tests hadn't covered every case. They had, however, covered the case in question.
| [reply] [Watch: Dir/Any] |
|
It is indeed possible to set $! in user code.
perl -e 'print ("$_ gives ", ($!=$_), "\n") for 1..20'
However you will note that its behaviour is highly magical
and (unless you do some magic of your own) not particularly
usable for any purpose other than its intended. | [reply] [Watch: Dir/Any] [d/l] |
|
122 gives Disk quota exceeded
123 gives No medium found
124 gives Wrong medium type
125 gives Unknown error 125
126 gives Unknown error 126
127 gives Unknown error 127
Think a patch to set error 125 to 'Just another Perl hacker' would be frowned upon... ;-)
-Blake
| [reply] [Watch: Dir/Any] [d/l] |
Re: An example of programming by contract
by blakem (Monsignor) on Nov 26, 2001 at 07:45 UTC
|
function to return both a false value and also set an error string
I hate to add a second semi-serious node to this thread but that reminds me of Fastolfe rather clever attempt to solve the same problem. He essentially creates strings that evaluate to false in boolean context... So, you can return false-with-an-error-message all in one scalar. Even the author doesn't recommend it for production use (since false-strings are non-intuitive to the sane perl programmer) but it is still an interesting approach.
-Blake
| [reply] [Watch: Dir/Any] |
Re: An example of programming by contract
by qslack (Scribe) on Nov 26, 2001 at 10:12 UTC
|
Well, in response to sub validate_integer { 1 } # how do you do this?
...you could do... sub validate_integer {$_[0] == int($_[0])}
I assume, of course, that by "validate integer" you meant to check if the number was an integer.
Quinn Slack
perl -e 's ssfhjok qupyrqs&&tr sfjkohpyuqrspitnrapj"hs&&eval&&s&&&' | [reply] [Watch: Dir/Any] [d/l] [select] |
|
die "Gotcha!" if validate_integer("not an integer");
Personally I either wouldn't have the test, or else I would
make the test be an RE pattern. eg:
sub is_integer {
$_[0] =~ /^-?\d+\z/;
}
| [reply] [Watch: Dir/Any] [d/l] [select] |
Programming by Contract Example. revised
by princepawn (Parson) on Nov 26, 2001 at 23:07 UTC
|
This example takes the advice of tilly and chromatic with respect to how to write validation routines in a saner fashion.
=head1 METHODS
=head2 factorial
INPUT : a non-negative integer, n
OUTPUT: the factorial of n
=cut
sub factorial {
my $number = shift;
return 1 if $number <= 1;
return ($number * factorial($number-1)) ;
}
our %validate =
(
non_negative => sub { $_[0] >= 0 },
integer => sub { 1 }
);
sub validate {
my $input = shift;
my %arg = @_;
for (@{$arg{as}}) {
my $func = $validate{$_};
my $r = $func->($input);
die "$_ validation failed" unless $r ;
}
return 1;
}
sub get {
print "input? ";
$_[0] = <>;
}
my $input;
get($input)
and validate($input, as => [qw(non_negative integer)])
and print factorial($input);
| [reply] [Watch: Dir/Any] [d/l] |
|
validate($input, as => ['blah']);
Since $validate{'blah'} doesn't exist, you end up with a fatal error. A simple (if uninformative) change would be to
for (@{$args{as}}) {
my $func = $validate{$_} || next;
my $r = $func->($input);
die "$_ validation failed" unless $r;
}
Better would be to throw some exception or warning.
Furthermore, you do know that this will be less efficient, execution-wise, than just putting the checks into the program. This methdology is best if you plan on doing the same checks on a large variety of inputs. (Of course, you're just playing, which is cool.)
One possibility is to, instead of forcing the user to validate the input, have the function validate its own input using these functions. Or, maybe a wrapper around factorial(). That way, everything is encapsulated.
------ We are the carpenters and bricklayers of the Information Age. Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement. | [reply] [Watch: Dir/Any] [d/l] [select] |