Tuesday, February 10, 2015

Fix Those Legacy Subroutines or Methods

Maybe you know the feeling… you go to add an option to that method or subroutine and… cue Jaws theme

sub update_shopping_cart {
    my $cart_id        = shift;
    my $item           = shift;
    my $quantity       = shift;

Argh. You don’t want your legacy code to break but you also don’t want to add a fourth unnamed parameter to the existing problem. And the solution is simple:

sub update_shopping_cart {
    my $cart_id        = shift;
    my $item           = shift;
    my $quantity       = shift;
    my $apply_discount = 0;        # Initialize the fourth parameter

    my $param1 = $cart_id;
    if ( ref $param1 eq 'HASH' ) {
        $cart_id        = $param1->{cart_id};
        $item           = $param1->{item};
        $quantity       = $param1->{quantity};
        $apply_discount = $param1->{apply_discount};

Now either of these work. The legacy call:

update_shopping_cart( 314, 'apples', 3 );

…or the new style:

    cart_id        => 314,
    item           => 'apples',
    quantity       => 3,
    apply_discount => 1,

Bonus: there is no way to use the new option with the old-style call. If someone wants to use it, they’ll need to switch to the new style.

  • The new call is self-documenting. In the original form of the call, you see “314” in the code by itself, and it’s not immediately obvious what it is. Now it’s nicely labeled.
  • Now that you have added the new format, you can painlessly add additional named parameters as needed.

  • It may be confusing to see two different styles of calls in your codebase. But, now you can transition the old code piecemeal.


Mark said...

I might add another con in that when you come to testing this sub you will now need to check both calling styles.

Anonymous said...

I was about to start using this, and then realized that my codebase is probably so brutally hacked together that I have some cases where I'm already passing a hash ref as the the first argument followed by arbitrary lists of extra arguments. Still, this is an excellent tip, thanks!

bigfoot said...

I often do the same. I guess one might call this the poor man's way to create polymorphic methods in Perl. It's the extreme of ad hoc polymorphism...cool, but ultimately a hack to overcome Perl's signatures and of course typing...but I'm not complaining.

Given my druthers, I like the flexibility Perl gives me to create my own overloaded methods, but I also find this technique can become ruinous to reuse if you don't document your clever method's various implementations copiously. Cleverness always has its price.

Pod, pod, and more pod! if you are going to create alternate implementations of methods.