oscarbonilla.com
18Aug/080

The case against snippets

Back when TextMate was relatively new and that famous video of ruby on rails was making the rounds. I watched in amazement with the rest of the nerds as code flew around the screen. I had caught Snippet Fever.

After using TextMate for a while I became addicted to snippets. The basic idea of a snippet is that you can write a few characters and they expand to whatever you want. For example, you can type: 'fl', hit Tab and TextMate will write:

  for(int i = 0; i < ... ; i++) {
    ...		     
  }

Where ... is where the insertion point will be each time you hit tab. You can do the same with Emacs in many different ways: Emacs skeletons, the snippet.el plugging, hand-coded elisp, etc. The point of this post, however, is not to show all the different ways that you can get snippets. The point is to ask whether it's worth using snippets at all.

Snippets eliminate the need to type recurrent idioms. If you have to write accessors, it's very pleasant to type 'set', hit tab and watch the screen fill with 20 lines of code that just wrap a field in a class with a setter function. But this is fixing the problem in the wrong place.

The fundamental problem is that the language is too damn verbose.

Imagine English had no pronouns so if I wanted to tell you a story about my friend Tim and me, I had to write:

Tim had called Oscar to ask Oscar if Oscar could lend Tim the book that Oscar had bought the day before. Oscar told Tim that Oscar was still reading it, but that Oscar would be happy to lend Tim Oscar's book when Oscar was done with the book so that Tim could read it.

The above paragraph is too verbose. It's hard to write. Snippets would help me write it by automatically filling all the appropriate places with "Tim" and "Oscar".

If the language you are using is so verbose that you need snippets, how about fixing the language?

Even on languages that don't allow fixing the syntax you can still do something.

For example, in C, if you find yourself typing a for loop to access a list over and over again:

  for (int i = 0; i < length(list); i++) {
 
  }

Make a small pre-processor macro that abstracts that:

  #define EACH(list) for (int i = 0; i < length(list); i++)

And now you can write:

  EACH(list) printf("%s\n", list[i]);

And since code is usually read many more times than it is written, you have helped make the source base a little better.

20Dec/065

Code Tells You How
Revision History Tells You Why

I had a little rant on commenting style and it seems that Jeff Atwood has beat me to it. Well, here's my rant anyway.

There are three different components I care about when reading code: "The How", "The Why", and "The What".

"The How" is the code. The very essence of code is to tell the computer how to do something. It's not 'what' to do, because that is open to interpretation, you might be trying to do something (what) and telling the computer to do something else (how). I don't need comments for the how, I can read the code. An example of a bad "how" comment is:

/* assign 0 to all flags */
for (i = 0; i < length; i++) things[i]->flag = 0;

I can see you're setting all flags to zero. Thanks for the non-information.

"The What". This is the interpretation of "The How". Basically, it's what that code is trying to accomplish. For instance:

/* clear all flags */
for (i = 0; i < length; i++) things[i]->flags = 0;

At least now I know what zero means. But it still doesn't tell me a lot. As a matter of fact, it's rare when I need to know "The What". If I need comments to know "The What", the code is probably poorly written. I should be able to infer "The What" from "The How".

"The Why". This is what I really care about. Why is this piece of code like this? Why not some other way? What was in the programmer's mind when he wrote that piece of code? What alternatives were considered and discarded? Why were they discarded?

/* We're on a deadline and I don't understand why the flags
 * array is getting corrupted. Clearing all flags seems to
 * fix it, but there is a deeper issue here that needs
 * investigating
 */
for (i = 0; i < length; i++) things[i]->flags = 0;

Ah, much better now. I've gained some insight into the programmer's mind, so later when I'm working on the code I can make sense of this line. Basically, I can answer the question: "Why was this added?"

But wait, there are problems with this approach.

First of all, you're using up 5 lines of comments for 1 line of code. That's verbose, but not the worst of the problems.

What's worse is that if I'm just skimming the code because I'm trying to solve some other problem for which I don't really care about this particular line, you're going to make me stumble. I'm going to read that comment, remember that I once knew of a way in which the flags could get corrupted and spend the next 1/2 day chasing an issue that has a workaround. Your nice comments are on my face when I don't need them.

Even worse, this style also leads to comments needing maintenance. The code will change, and the comment won't be updated with the change. You'll get

/* We're on a deadline and I don't understand why the flags
 * array is getting corrupted. Clearing all flags seems to
 * fix it, but there is a deeper issue here that needs
 * investigating
 */
for (i = 0; i < length; i++) {
        if (things[i]->flags & FLAG_INIT) things[i]->flags = 0;
}

Whoa there buddy! You're saying that flags were getting corrupted and now you're using them? How can you trust them? Either the comment is wrong or the code is wrong.

What probably happened was that some programmer didn't read the comment (or read it but forgot to change it) and found a convenient place for putting his change. Now understanding this piece of code has been made harder than it should be.

And all of this is just looking at code statically. Code is organic, it grows, it changes, it evolves.

There aren't a lot of comments in the BitKeeper source base because we rely heavily on the Source Control system to answer the "Why" questions. So you'll see the above code as:

for (i = 0; i < length; i++) things[i]->flags = 0;

No comments whatsoever. However, if you need to understand what was going on, you can get the annotated version:

ob 1.123.21: for (i = 0; i < length; i++) things[i]->flags = 0;

and see the comments for the ChangeSet 1.123.21:

ChangeSet@1.123.21, 2006-07-21 10:45:34, ob +1 -0
  Fix bug 2004-10-21: app crashes when restarting
  src/main.c@1.23, 2006-06-29 19:40:46, ob +1 -0
    We're on a deadline and I don't understand why the flags
    array is getting corrupted. Clearing all flags seems to
    fix it, but there is a deeper issue here that needs
    investigating

Now the information is there, but it's not in my face unless I need it.

Note that when looking at code this way, the question "What changed?" is trivially answered by the diffs and therefore doesn't need to be answered by the comments. E.g. the following are really bad comments:

src/main.c@1.23, 2006-06-29 19:40:46, ob +1 -0
  Add code to clear the flags

Well, duh! I can see that from the diffs!

@@ -274,6 +278,7 @@
   things = getThings(x, y, z);
   length = getLenght(things);
+  for (i = 0; i < length; i++) things[i]->flags = 0;
   doStuff(things);
   }

I can see that you added code to clear the flags, why did you do it? That's the interesting bit.

Technorati Tags: ,

Tagged as: 5 Comments