Monday, December 10, 2007

Introduction (First Response)

First Things First
Let's start with the basics of what to do when you're presented with someone else's code to maintain:

Don't panic!

First, you have a strategic decision to make: Do you want this responsibility or not? Your best if not only chance to reject this assignment—if that is an option at all—with any semblance of honor may be right at the outset, even though you have nowhere near enough information to tell you how arduous it will turn out to be. If you can, stall for time so you can find out more about what this project entails.

Let's assume that you've decided that the best thing you can do for your career right now is to take over this code (even if you think you have no choice in the matter, this is a much more empowering way to view it). Your first duty is:

Find the author!

Only superhuman developers will have left you so many resources for maintaining this code that your job won't be massively eased by personal contact with them. Even if they're in the process of cleaning out their desk while a security guard hovers impatiently nearby, track them down now and ask them:

Did anyone else work on this code? Who?

Are there any other files or data that weren't given to you that would be useful?

Are there any documentation, engineering notebooks, or Post-it® notes stuck to the monitor that pertain to the project? The programmer may be in the process of round-filing them as you speak.

If the developer is not on the verge of being ejected involuntarily from the premises,[1] find out whether they have any time to work with you on taking over this project. Tell your supervisor that "pair programming" is a good thing according to the popular Extreme Programming methodology[2] and this is a golden opportunity to try it out. If all else fails, consider buying the former developer lunch for a week out of your own money. You'll still end up so far ahead of the game with what you learn from them that you'll make it back in compensation or kudos. However, the developer will probably spend some time with you for nothing if you tell them you want their help in learning how they created their code; most people are touched by such flattery.
Reasons for Inheritance
It's important to know why people inherit code, because you might jump to the wrong conclusion about which one applies in your case. Perhaps:

The original developer is gone.

The original developer is bored with the program and wants to be rid of it.

The original developer is reassigned or otherwise has no time to spare. (Don't give up; this might be more negotiable than it first appears.)

The responsibility for the function performed by the program has been reassigned from the original developer's group to your group.

You might also have been brought on board because the program might need to be modified in ways that are within your ability but are beyond the capacity of the original developer:

The program needs to be ported from one environment to another.

The performance of the program needs to be improved.

The program needs a data interface that uses a new protocol or format.

The program has expanded to the point where it has to be restructured to make further modification cost-effective.

The program performs operations that are needed by other programs and these are to be collected into object-oriented modules for easy reuse.

Here are some extremely unpleasant but feasible scenarios:

The program has grown so large that even the original developer cannot figure out how to make a change to it.

The program lives in a very brittle environment of many dependencies on fragile or operational systems and any change or upgrade is bound to break one of them.

The program has not been documented and the original developer has been successful at convincing management that such a task is beneath his or her dignity.

There are also a number of unsavory possibilities motivated by politics, such as an attempt to sabotage the reputation of you or your group by giving you an impossible task. Or you might have been given the project simply as something to keep you busy. Sad to say, going into information technology is no way to escape politics.
Observe the Program in Its Natural Habitat
You need to know as soon as possible that the program actually works. Don't assume that it works just because you're told so! Perhaps you're not in the group that's responsible for the current operation of this program and you're supposed to get it to do something else. Go to the original owners and watch them run the program. Then check that the program they ran is identical to the one you were given.[3] Then figure out how you can run the program yourself without making any modifications. This might not be as easy as it sounds; the program might depend on things like files you're not permitted access to or accounts you don't have. There might not be a test environment that you can try the program out on (more on this in Chapter 3). However, it is vital that you can reproduce the successful operation of the program you have just witnessed with as few modifications as possible.
Get Personal
Use any personal knowledge that you have of the developer, or find out what you can about how they program. Are they inclined to test and be thorough, or do they frequently make mistakes and goof off? If you don't know anything about them, don't assume they knew what they were doing and then puzzle endlessly over a strange fragment of code. That code might be irrelevant or just wrong, and left in for no good reason. Knowing the habits and level of sophistication of the developer will be invaluable in deciphering the program.

Knowing something about the programming background of the developer could also be helpful. If they have, for instance, much experience in C but little in Perl, their Perl programs may look like C code. Don't spend a lot of time puzzling over why they have neglected efficient mechanisms such as list returns from functions or hashes when the cause is quite probably that they have discovered that they can program Perl "just like C" (or Java, or bash, or . . . )
Strictness
The most common cause for grief in inheriting a Perl program is the absence of these two magic lines from the beginning of every file:


use warnings;

use strict;


If the program contains a -w on the #! line, that counts the same as a use warnings in the main program and every module that it uses. Because the use warnings pragma is a relatively recent addition to Perl (version 5.6.0 or later) you're more likely to find the -w flag in inherited programs.

Why these lines are not the default for Perl programs has been the source of much contention between certain people in the Perl community, including me. But they aren't the default, so we're stuck with the situation. Omitting these lines from a Perl program is as close to professional suicide as you can get this side of putting a whoopie cushion on the CEO's boardroom chair. In case you're not aware of how important these lines are, I'll go into more details on their effects in Chapter 5. First let's deal with what you should do if you are handed code that lacks them.

If you have the opportunity and the chutzpah, hand the program back to the developer who wrote it, and as politely as you can, ask them to add use strict and use warnings (or -w) to the code, and let you have it back after they have fixed the code so that it runs without errors or warnings. (If the code includes modules written by the developer, each one of them should include a use strict at the top, and a use warnings if the main program isn't using the -w flag.)

This is not the time to tell them that doing this will almost certainly result in dozens of fatal errors, hundreds of warnings, and numerous changes required of the code in order to make it compliant with these new directives.

They will think that your request is a trivial addition. Let them. The changes they make to enforce compliance will make your job immeasurably easier, because the way they choose to resolve each error or warning will be based on understanding of the code that they have and you do not.

If you think this tactic is underhanded, you haven't yet tried to maintain a significant body of code that was developed without use strict and use warnings. After doing that, you'll be inclined to approve far more stringent forms of punishment for a developer who omits them. The rack, for instance.
Warnings
The question often arises of whether to leave warnings enabled in production code. Those against doing so say that you don't want the customer seeing "dirty laundry" generated by your program—what if something quite innocuous in your program today turns out to be the subject of a warning added to a later version of Perl?

I prefer to leave the warnings in production programs. My point of view is that warnings are like strictness exceptions, only pickier, and should be treated the same: Either code to avoid the warning, or explicitly disable the warning around code where:

You know why the warning is being generated; and

You know that the warning is unlikely to be generated for any other reason; and

It's clearer to disable the warning than to recode.

If a new warning is added to Perl, the odds are that you'd want to know if you had code that triggered it. Today's warnings could become tomorrow's fatal error if you're using a feature that is being deprecated.

Don't scratch the itch to turn warnings off merely because they are inconvenient. Think of them instead as a programming mentor nagging you to make your code robust. You might respond, "No, I know what I'm doing, so don't complain about this again," by disabling the warning; or you might change the code.

For instance, suppose you are writing code that looks up people in a database by their username and inserts their e-mail address in a message template:


if (my $user = $db->lookup_user(username => $username))

{

my $email = $user->email;

open MAIL, "|$SENDMAIL" or croak "Mail: $!";

print MAIL <<"EOMAIL";

To: archive\@listserver,$email

Subject: Your account

[...]

EOMAIL

close MAIL;

}


Testing may go just fine. But suppose one day a user comes along who doesn't have a registered e-mail address, and so the email() accessor method returns undef. Without warnings, there will be no clue that the user didn't get the message they deserved unless someone inspects the e-mail archive that was copied.

I am not saying that the best way to write that e-mail-sending code is to allow the warning facility to catch the case where the address is undefined. It would have been better to have written something like:


if (my $user = $db->lookup_user(username => $username))

{

if (my $email = $user->email)

{

open MAIL, "|$SENDMAIL" or croak "Mail: $!";

print MAIL <<"EOMAIL";

To: archive\@listserver,$email

Subject: Your account

[...]

EOMAIL

close MAIL;

}

else

{

# Handle the user not having an email address

}

}


But you don't always have that much foresight. Leaving warnings enabled helps make up for that shortcoming.

I'll go into warnings and strictness in great detail in Chapter 5.

1.7.1 What Were They Doing?
Find out especially what the developer was optimizing their code for. Before you discard a complicated section in favor of something much simpler, find out whether it was written that way to meet performance requirements. Then find out whether it's still necessary to use those optimizations to meet the current requirements.

Code could have been optimized for several different factors, some of which I illustrate here with sample code, each example doing exactly the same thing (as explained by the comment in the first one).

Maintainability:


# Print words with an even number of letters, AND even

# number of each vowel, AND even position in the input

OUTER: while (<>)

{

next if $. % 2;

chomp;

next if length() % 2;

for my $vowel (qw/a e i o u y/)

{

my @vowels = /$vowel/g;

next OUTER if @vowels % 2;

}

print "$_\n";

}


Performance:


while (<>)

{

next if ($. | (length() - 1)) % 2;

next if tr/a// % 2;

next if tr/e// % 2;

next if tr/i// % 2;

next if tr/o// % 2;

next if tr/u// % 2;

next if tr/y// % 2;

print;

}


Brevity:


#!/usr/bin/perl -ln

($x=aeiouy)=~s#.#y/$&//|#g;eval("$x$.|y///c")%2&&next;print


Job security:


@i = map { chomp; $x++ %2 ? $_ : () } <>;

while ($i = shift @i)

{

ord(pack "w/a*", $i) & 1 and next;

$_ = "$i\n";

$i =~ s/$_(.*)$_/$1/ for qw/a e i o u y/;

print unless $i =~ /[aeiouy]/;

}


As you can see, the Perl philosophy of "There's More Than One Way To Do It" (TMTOWTDI) can result in radically different code, depending on whether the author wants it to be readable, fast, as short as possible, or incomprehensible, respectively.

Remember that it is the norm for programs to grow by evolution and accretion. The more successful the program was, the more likely it is to have had other functionality grafted onto it in response to requirements creep. Don't spend a long time trying to figure out the design rationale of a program; there may not be one left.

No comments: