Nathan's Lucubrations; on computers, music and the outdoors

20 03 2011

Sun, 20 Mar 2011

Show, don't tell

It's an old maxim that in writing, you should show, don't tell. Don't say a character is happy, have him literally jump for joy. To some extent, this precludes a third person point of view, obviating the need for an omniscient narrator. Sometimes that can be a good thing, to let your audience get a better feel for being in the situation you are describing, helping them suspend their disbelief.

This carries over into other fields. For instance, there are those who hold that any film that starts with with textual exposition has points against it. Some even go so far as saying that telling or showing too much can ruin the horror of an unseen nemesis (cf. Alien vs. Aliens).

Even more interesting is where it carries over to other seemingly unrelated fields, say web design. Or programming. Far too often, I will see code like this:

// Count the number of doodads.
int ii;
for(ii = 0; doodads[ii] != NULL; ii++);

Is it concise? Sure. Will it work? Maybe, if doodads has a NULL pointer as its last element. But is that comment really necessary? I've always believed that code should tell the "how" and comments should tell the "why"; if your code doesn't tell the "how", then it needs to be rewritten to be more understandable. Two simple changes could make this code better:

size_t num_doodads;
for(num_doodads = 0; doodads[num_doodads] != NULL; num_doodads++);

Change the variable name and the comment becomes unnecessary. Better yet, if you are using an STL container, let it do the work for you:

const size_t num_doodads(doodads.size());

That's if you really need the number of doodads and are assuming it won't change; it's probably best just to use the member size() call everywhere you need it. Also, don't rule out iterators; they may seem like a silly concept at first, but the nice part is that they work with any container, so changing out containers becomes dead simple, with the proper forethought:

typedef vector<int> Container;
typedef vector<int>::iterator Iterator;
Container myCollection(10);
for(Iterator itr(myCollection.begin());
    itr != myCollection.end();
  cout << (*itr) << endl;

By changing two tokens (the two vector tokens in the typedefs, to say deque or list), you can get different performance characteristics and tune for your particular application. Of course, you have already profiled the code to make sure that the biggest slowdown is because of using vector, right? In the end, though, this really helps readability, because to other programmers, it is clear that you are using a Container and you are iterating through all its elements; the underlying implementation doesn't matter.

Quotes to live by:

Programs must be written for people to read, and only incidentally for machines to execute. -- Abelson & Sussman, SICP, preface to the first edition
Any fool can write a program that the computer can understand. It takes a good programmer to write a program that other people can understand. -- Martin Fowler
I can't tell what the hell his code does, it's mostly comments. -- Adam Radford

posted at: 17:13 | path: | permanent link to this entry

powered by blosxom