Tuesday, September 7, 2010

Lipstick For the Evented Programming Pig

Previously, I have complained about how ugly code ends up looking with the evented programming paradigm. A pattern that might became vitally important for performance reasons, but nevertheless makes for super ugly code. I came across another similar issue recently, courtesy of a certain Stack Overflow post meant to demonstrate File I/O in every programming language.

The problem is to write "Hello" to a file, append "World" to the file and then to print onto the screen the last line of the file. (which ends up being "World" ... SHOCKU). I've stolen the solution from node.js, everyone's new favorite technology that will save the world.

Ugly, ugly, ugly code. Kill it with fire!! Now to be fair, this is partly because the node apis are quite low level. We might be able to look forward to an fs.append method someday for example.

I've heard people argue that it's just a matter of getting used to it ... I acknowledge that this is true. But it is true of all sorts of ugly code that adds a little more mental effort every time you parse it. I mean code I used to write back in college had high Cyclomatic complexity and could be difficult to follow deep inside a nested method. But back then I was a whiz at following it. I just didn't see a problem. But the truth is it took me a little effort whenever I read or modified it.

This is important because we all know that "code is read more often than it is written" right? So while you may get used to reading large, complex blocks of nested code, it takes its toll on you.

So the obvious next step is to start naming pieces of your code and pulling it out. Something like this.

Better, but still a bit ugly. But more importantly it forces you to write your code literally backwards. I'm sure you could find ways to prevent that backward effect locally, but it will remain an effort. The Stack Overflow post has something similar but that at least nicely breaks it up into composable pieces.

The other day I was discussing the same issue with Rakesh Pai, and Joel. What such code would look like ideally. We played "what if?". What if we could modify javascript however we saw fit and wrote sequential code that was converted by a machine to trivially nested code. Afterall programming languages are for people not machines.

Suppose say I could pass '...' to a function and that meant "take the next function and pass it as a callback to this one". And say '...' on a seperate line means that the remaining code below that line is a callback. So we get this.

Now this, is much better. It might be highly impractical, but something on those lines might work if we could change javascript as we saw fit. (say we had macros for example). I have grouped the lines of code into sections that might ideally become methods. (well basically we could create our append method there).

However, while we cannot change javascript, at least javascript has first class functions. So a library called step.js makes something similar possible. Take a look at the same solution with step.js.

Its pretty close to what we had, though there is a great deal of cruft with all those function wrappers. I mean, it's pretty much the same problem that you have with ruby. The only way to pass code around to be executed later is as a lambda.

Still its way better than what we started with. This compares quite nicely with our previous code. Except everything needs a function wrapper and the magic variable used is by clever use of "this". But I think given the popularity of node.js, step.js might be very useful to organize and improve readability of code. And if some effort is put into also making it easy to debug, it might be used all over the place.

Edit: I forgot to link to the library last time. Get Step.js Here.

3 comments:

  1. Nice summary of some of the issues with event/callback oriented programming in JS. Some people on the Stack Overflow page also mentioned that continuations would help here -- that would be an interesting thing to explore.

    ReplyDelete
  2. Thanks cg. I'm not sure how the code would look, but I guess I see why it looks like continuations.

    ReplyDelete
  3. I haven't looked into step.js in much detail, but an alternative library can be created that offers a more readable syntax:

    var path = "fileio.txt";

    step(fs)
    .writeFile(path)
    .open(path, "a", 0666)
    .fn(function (err, file) {
    this.write(file, "\nworld", null, "utf-8");
    })
    .close()
    .readFile(path, "utf-8")
    .fn(function (err, data) {
    var lines = data.split("\n");
    sys.puts(lines[1]);
    })
    .runSteps();

    Here, the "step" function essentially creates a wrapper object around any object passed to it. However rather than executing each function immediately, it stores the function name and arguments inside an array, and finally runs them with the call to runSteps().

    ReplyDelete