My favorite refactoring is Replace Conditional with Polymorphism. If I were to make alist of everything I've learned the hard way in programming, this refactoring would be at the top of the list near "Make it easy to test always" and "Make it difficult to misuse".
I'm sympathetic to the view in interface design that the fewer choices, the better. I believe that push for simplicity comes from the desire to force the design to consider the right choices. If you get the default behaviors right, if you choose the right things to make easy and obvious, and if you don't forbid people from extending your code or product to do special things, you have achieved something lovely and worthwhile.
When we design things, we have to find a balance of flexibility (people should be able to use this for purposes we can't imagine right now) and simplicity (we should emphasize the few things we do well and right). That goes for writing code, from designing APIs to each line of code we right.
APIs are user interfaces for programmers, sure. The individual lines of code we write—within our functions and our methods and our loop bodies—are APIs for understanding the problems we're trying to solve.
When I write code, I want to write boring code. I want to write code so boring that it's easy to overlook the code because the intent of the code is so staggeringly obvious. Boring code is straightforward. Boring code doesn't have a lot of corner cases to get hung up on. Boring code you can glance over and see what it's doing.
Boring code gets out of your way.
A lot of the code I've written recently looks like this:
sub method
{
my ($self, $collection) = @_;
while (my $item = $collection->next)
{
next if $self->do_this( $item );
$self->do_that( $item );
}
}
Sometimes there are preconditions:
sub method
{
my ($self, $collection) = @_;
return unless $collection->has_some_attribute;
while (my $item = $collection->next)
{
next if $self->do_this( $item );
$self->do_that( $item );
}
}
Sometimes there are postconditions:
sub method
{
my ($self, $collection) = @_;
my @failures;
while (my $item = $collection->next)
{
next if $self->do_this( $item );
$self->do_that( $item );
push @failures, $item;
}
return unless @failures;
return \@failures;
}
I like this form less than the previous two; there's too much structural code for my taste. It's still pretty boring though.
One of my favorite features of generic or polymorphic programming is that you can push the "What should I do if this condition does not apply?" question into methods or pattern matches (of the Common Lisp or ML or Haskell sense, not regular expressions). Dealing with an empty list in Haskell is easy:
sumList [] = 0
sumList (x:xs) = x + sumList xs
... and the corresponding Perl is:
sub sum_list
{
return 0 unless @_;
my ($first, @last) = @_;
return $first + sum_list( @last );
}
Math's a bad example for OO polymorphism, but I would rather write:
for my $item (@collection)
{
$item->save;
}
... than:
for my $item (@collection)
{
$item->save if $item->>is_dirty;
}
... and let save()
decide what to do. When I phrase it like
that, it's obvious. It's the object's responsibility to decide what to do.
Maybe that's why non-guard-clause conditionals bother me more and more: they're
a sign that I need to move around responsibilities more to cluster them where
they really belong. As silly as it seems to say that a little word like
if
or unless
is a sign something's wrong, those
little words can be a sign that the current code under consideration is doing
too much. It needs to be simpler.
(There's a friction, of course, to breaking classes into smaller domains of responsibility, but that's the subject of a different post.)
My rule of thumb about when I'm allowed a conditional is that ... if $self->query() is usually fine (until it becomes a message that I need a new class) but that ... if $that->query() is usually wrong and ... if $that->attribute() eq ... is a huge red flag. The Smalltalk pattern that holds that accessors go in the 'private' protocol is one that makes more and more sense to me.
I wish Perl 5 had classes with less inertia. It wouldn't have to go as far as Smalltalk, but if I felt classes were lighter and more agile, I'd use a lot more little classes.
Am I the only one?