When You Can't Write Methods Too Small

| 1 Comment

I do a lot of exploratory programming. I prefer it, even. It's much more satisfying (and effective) to sketch out a simple design for the few most important features of a project, implement those, then analyze the whole thing in light of what I need to write next.

Often the experience of solving a problem that way helps me to understand what I did right and what I did wrong. What was difficult to write? What was difficult to test? What will I have to change to account for new features?

Refactoring, in my mind, is a disciplined process of rearranging source code to improve its maintainability, its correctness, and its testability. It's satisfying to extract a third entity (a class or a role) from two other entities. It's very satisfying to collapse several duplicate or near-duplicate pieces of code into a single line. It's powerful to delete code and fix bugs and add features.

One of the main benefits I've achieved from learning Haskell is the profound understanding that a function composed of two or three lines of pure code is easy to test and easy to reuse. The same goes for Smalltalk, where a method of two or three lines may not be pure, but it's very powerful and often very reusable.

In Perl 5, this refactoring usually follows several stages: from procedural code to objects to roles to very small methods. In that final stage, a handful of design constraints govern how I approach my refactorings:

  • Can I name the extracted method appropriately?
  • Does it have a single obvious return value?
  • Does it need more than two or three parameters? (A list of items over which to iterate counts as a single parameter.)
  • If I were to use mocking to test the method, how many passes do I need to make through it to get full coverage? How much external data do I need to set up and tear down to test it appropriately?

(Similar questions apply to refactoring C code, but C's relative lack of abstractions compared to Perl 5—let alone modern Perl 5 with Moose—makes that work more difficult. You can circumnavigate a sea of function pointers stored in structs and callbacks and dummy parameters you need sometimes and don't other times and those lovely casts to void *, but at some point it's nice to join the state of the art circa 1985 at least.)

The result tends to be methods of five or six lines in length (not counting BSD-style braces and copious vertical whitespace). Now imagine if core Perl let me write:

method find_comments
{
    my $content = $self->content();

    return $1 if $content =~ /(\d+)\s+comments/i;
              || $content =~ /comments:?\s+(\d+)/i;

    return 0;
}

Saving a line or two on every one of these small methods is suddenly significant. Perhaps a philosophical (some might say sardonic) way to describe refactoring is "the systemic process of discovering the limitations of expressiveness and abstraction of your current programming language."

1 Comment

While it's not strictly functional, I think that Moose has forced me to think in much smaller methods due mostly because of method modifiers. When I'm in the procedural mind set I tend to see a method as a way to solve a problem, where with moose I tend to see that same method as a collection of steps to get me to the same result.

To overcomplicated your find_comments example, I would have been split up internals to become something like :

method find_comments {
   $self->has_comments ? $self->extract_comments : [];
}

This way if I ever have a subclass that has a different expectation I can then just wrap that part as needed with out having to muttle up the expectations of every other user. It's not the best example as find would be simple enough to just wrap on its own, but it gets the point across.

Modern Perl: The Book

cover image for Modern Perl: the book

The best Perl Programmers read Modern Perl: The Book.

sponsored by the How to Make a Smoothie guide

Categories

Pages

About this Entry

This page contains a single entry by chromatic published on November 9, 2010 1:51 PM.

Composability, Scoping, and Non-Interference was the previous entry in this blog.

Compile-Time Pollution Checking is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.


Powered by the Perl programming language

what is programming?