(aka known as which language am I using again?)
Dave Rolsky has a new post on perl 5 overloading. It’s fairly informative, but it contains this little gem (emphasis mine):
Defensive programming, for the purposes of this entry, can be defined as "checking sub/method arguments for sanity".
Blanket statements like this really get my gripe up. Let’s have a look at defensive argument checking in Perl taken to its illogical conclusion.
Defensive Primitive Type Checking
use strict; use warnings; use Carp; use Scalar::Util 'looks_like_number'; sub can_legally_drink { my $age = shift; croak "$age is not a number" unless looks_like_number($age); return $age >= 18; } print can_legally_drink('x'), "\n";
And fantastic news! can_legally_drink correctly detected that my argument isn’t a number.
x is not a number at age.pl line 10
main::can_legally_drink('x') called at age.pl line 14
But hang on a minute. Not all integers are ages. Surely we want to check if a real age type was passed in.
Checking For A ‘Real’ Type
My stripped down defensive age type might look something like this.
package age; use Carp; use Scalar::Util 'looks_like_number'; sub isa_age { my $arg = shift; return ref($arg) and blessed $arg and $arg->isa('age'); } sub new { my ($class, $years) = @_; croak "$years is not a number" unless looks_like_number($years); bless { years => $years }, $class; } sub years { return $_[0]->{'years'} } sub less_than { my ($self, $other) = @_; croak "$other is not an age" unless isa_age($other); return $self->years() < $other->years(); }
And then my calling code can look like this:
package main; sub can_legally_drink { my $age = shift; croak "$age is not an age" unless $age->isa('age'); return ! $age->less_than(age->new(18)); } print can_legally_drink(age->new(18)), "\n"; print can_legally_drink(18), "\n";
And woohoo, the second line throws an error as I wanted.
Actually, I don’t write Perl like this. Dave, you probably want to avoid working on code with me, thanks.
Moose
To be fair, Rolsky is talking his own book. Moose has a bunch of stuff that handles all this type checking malarky nice and cleanly. If you’re building something big in Perl, you should take a look.
But if you really care about types, I mean defensive programming that much, you could use a statically typed language instead and then you even get fast code thrown in for free.
Hi Jared,
my take on this is that sanity checks are trading development time for debugging time. Since latter usually takes more effort, it seems to be logical to favor sanity checks.
However on every job I’ve been so far there was great push to develop on time, typically in almost impossible schedule. Bug fixing was always taken much less strict and kind of second plan.
So I end up making those sanity checks only in code for other people (general library or module) and on few places where I or my coworkers got bitten. It also means very often that interface is not very intuitive then.
– Roman
I said “[i]f you don’t care about defensive programming”, which is not the same as saying “turn Perl into Haskell” (though Haskell’s type system is pretty cool).
In the case of age, I’d check for a *positive* number (maybe an integer). That’s good enough in a language like Perl 5.
I don’t like how lots of people see type checking as an all or nothing affair. Why should I use a static language when I want to do thorough type checking at my boundaries (public facing functions)? I should be able to provide my library users with good error messages as early as possible without being accused of trying to turn Perl into Java or Haskell.
@Roman – a couple of points on your points… firstly, I agree with you that modules used by more people (usually more generic modules), in general should have better argument checking.
Secondly, often if I pass the wrong time of argument into a function, it gets picked up when I try to use it. For example, if I call prepare on an argument that isn’t a database handle, I get a fatal error at that line. As my functions are usually pretty small, I can immediately see what I’ve done wrong without an additional sanity check on the argument list.
@Dave – Hmmm… this kind of typeful programming is available even to mainstream languages like Java and C++. It isn’t the preserve of the Haskell-using elite.
And sanitising an age argument to ensure it is a positive number is hardly defensive. It doesn’t protect against argument inversion if I’m passing a couple of non-numbers, one of which isn’t an age. But wouldn’t it have been great if Ada had won and we could easily specify restricted subtypes of primitives that were checked by the compiler?
@Michael – I’m not sure whether you’re agreeing with me or not. My point is not that you shouldn’t sanitise arguments (whether that be in the defensive way I’ve illustrated here, or the soft way that Dave advocates above). I don’t really care how you code. What I do care about is when people say stuff like if you don’t sanitise your arguments, I don’t want to work with you.
Having said that, if you are specifying all your types precisely as I’ve indicated here, why wouldn’t you use Java and get greater speed, better tools, and in general support from the compiler for this style of programming?