Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Making Sense of Subroutines
by Rob Kinyon | Pages: 1, 2, 3, 4

Consider the following example of a convoluted conditional:

if ((($x > 3 && $x<12) || ($x>15 && $x<23)) &&
    (($y<2260 && $y>2240) || ($z>foo_bar() && $z<bar_foo()))) {

It's very hard to exactly what's going on. Some judicious white space can help, as can improved layout. That leaves:

if (
     (
       ( $x > 3 && $x < 12) || ($x > 15 && $x < 23)
     )
     &&
     (
       ($y < 2260 && $y > 2240) || ($z > foo_bar() && $z < bar_foo())
     )
   )
{

Gah, that's almost worse. Enter a subroutine to the rescue:

sub is_between {
    my ($value, $left, $right) = @_;

    return ( $left < $value && $value < $right );
}

if (
    ( is_between( $x, 3, 12 ) ||
      is_between( $x, 15, 23 )
    ) && (
      is_between( $y, 2240, 2260 ) ||
      is_between( $z, foo_bar(), bar_foo() )
    ) {

That's so much easier to read. One thing to notice is that, in this case, the rewrite doesn't actually save any characters. In fact, this is slightly longer than the original version. Yet, it's easier to read, which makes it easier to both validate for correctness as well as to modify safely. (When writing this subroutine for the article, I actually found an error I had made--I had flipped the values for comparing $y so that the $y conditional could never be true.)

How Do I Know if I'm Doing It Right?

Just as there are good sandwiches (turkey club on dark rye) and bad sandwiches (peanut butter and banana on Wonder bread), there are also good and bad subroutines. While writing good subroutines is very much an art form, there are several characteristics you can look for when writing good subroutines. A good subroutine is readable and has a well-defined interface, strong internal cohesion, and loose external coupling.

Readability

The best subroutines are concise--usually 25-50 lines long, which is one or two average screens in height. (While your screen might be 110 lines high, you will one day have to debug your code on a VT100 terminal at 3 a.m. on a Sunday.)

Part of being readable also means that the code isn't overly indented. The guidelines for the Linux kernel code include a statement that all code should be less 80 characters wide and that indentations should be eight characters wide. This is to discourage more than three levels of indentation. It's too hard to follow the logic flows with any more than that.

Well-Defined Interfaces

This means that you know all of the inputs and all of the outputs. Doing this allows you to muck with either side of this wall and, so long as you keep to the contract, you have a guarantee that the code on the other side of the interface will be safe from harm. This is also critical to good testing. By having a solid interface, you can write test suites to validate both the subroutine and to mock the subroutine to test the code that uses it.

Strong Internal Cohesion

Internal cohesion is about how strongly the lines of code within the subroutine relate to one another. Ideally, a subroutine does one thing and only one thing. This means that someone calling the subroutine can be confident that it will do only what they want to have done.

Loose External Coupling

This means that changes to code outside of the subroutine will not affect how the subroutine performs, and vice versa. This allows you to make changes within the subroutine safely. This is also known as having no side effects.

As an example, a loosely coupled subroutine should not access global variables unnecessarily. Proper scoping is critical for any variables you create in your subroutine, using the my keyword.

This also means that a subroutine should be able to run without depending upon other subroutines to be run before or after it. In functional programming, this means that the function is stateless.

Perl has global special variables (such as $_, @_, $?, $@, and $!). If you modify them, be sure to localize them with the local keyword.

Pages: 1, 2, 3, 4

Next Pagearrow