The random rantings of a concerned programmer.

Aug 16

bullshit rant, part 1

Category: Random

If you haven’t watched , consider doing so now.

I’m a real sucker for using Reddit/prog/it to choose what programming language/framework/libraries to use for my upcoming projects. Sometimes it’s worse than usual, and I find myself trying to figure out which project to shoehorn one thing or another into.

Naturally, right now I’m running high on the Node.js wave and tweaking along the Go rave. Naturally, I’ve had wildly different experiences with each.

I started using Node.js back when it was version 0.2.0, back before npm was widely used, when Express was first brewing, even before hipsters decided how to be cool. To be honest, since then it’s had a fairly stable API, and the ecosystem gets better every day. In my mind though, it still has two massive warts:

Continuation passing can make code pig disgusting

Quite a few people have bitched about continuation passing in Node.js being a pain for a variety of reasons.

It makes the code look like piss

This is not that big of a deal — if your code looks like piss because of the continuation passing style, then you’re not doing it right. Your code needs to either be restructured to accurately reflect what you’re trying to do, or you should use one of several existing libraries for manipulating control flow in a CPS-friendly manner.

Lexical scoping can make refactoring code “fun”

Almost every Node.js application is going to have code that looks like

function(foo, bar) {
  foo.do(function(x) {
    bar.do(function(y) {
      /* snip */
    });
  });
}

Assume that you want to lift the callback passed to foo.do into a separate, named function:

function lifted(x) {
  bar.do(function(y) {
    /* snip */
  });
}

The problem is that we’re referencing bar which was previously closed on from the parent environment. By lifting the function out of the lexical scope (presumably to use the same functionality elsewhere), we completely sacrifice all the automatic loveliness of closures. Instead, we have to explicitly go through the function to lift (which may be fairly long and complex, mind you), annotate exactly what’s used from the parent scope, then manually bind it into a closure:

function lifted(bar) {
  return function(x) {
    bar.do(function(y) {
      /* snip */
    });
  };
}

function(foo, bar) {
  foo.do(lifted(bar));
}

To add insult to injury, the compiler will accept whatever you give it, and fault you at runtime for any mistakes.

If you’ve got some Javascript curry magic, please let me know :(

When spaghetti is what’s on the menu

The biggest issue I have with continuation passing is that (since I’m dealing mostly with web applications) it’s exceedingly difficult to trace a failure back to a specific request. Error propagation in Node.js manifests itself in one of three forms:

  1. A thrown exception
  2. An 'error' event is emitted from an EventListener
  3. An error parameter is passed to a callback

Thrown Exceptions

Exceptions are used solely to communicate errors in synchronous calls, which naturally are few and far between. The handler is almost always in the same lexical scope of the calling code (since letting the exception propagate further may trigger a process.unhandledException event, which should almost always kill the application as cleanly as possible), so it’s not that big of a deal.

If you’re balking at the aforementioned “process.unhandledException should kill the application as cleanly as possible” — the logic here is that you really have no idea where the hell the exception came from, and even if you do, you have no access to the scope from which it was thrown. You can’t guarantee that your application is in a consistent state — your only recourse is to SHUT. DOWN. EVERYTHING. (if this is not the case and you can guarantee a consistent state, somehow, please let me know. that would be amazing).

Error Events

'error' events are fun little things — whenever you emit an 'error' event on any EventEmitter, if there are no listeners for that event, the process exits (I don’t believe that an process.unhandledException is emitted, even).

This actually works out really well, in most cases. Whenever you’re binding listeners for something, you should bind the error listener too — you’ve got all the stuff you need to identify from whence the error originated within lexical scope.

The one massive snafu is that when you’re abstracting an abstraction that uses EventEmitter internally, you MUST remember to handle and forward all the error events. You might be reading this and say “oh but that’s easy to remember it’ll never happen”. It actually happens more often than you think — the built-in http.Client functionality didn’t properly catch and forward errors from the internal net.Socket for a long time. You had to manually get the undocumented socket member and attach a listener manually. I think they fixed this in the new http.request interface.

Errors in callbacks — lexical scope strikes again

I think the most common form of error passing is by just returning an error code in a callback parameter. This works really well, until you start refactoring stuff and realize how much shit you’re stuffing in a closure.

Actually I don’t remember where I was going on this one — it seems that it’s trivially solved by just using the “pass it forward” error code C-ism.

Writing native (C++) extension is a bitchface

I uhh, this is getting kind of long. I’m gonna break this into a multipart/post and write up section 2 of Node.js bitchings and then eventually write up a section on Go bitchings. Hooray!


Tagged with: , , , , ,
7 comments

7 Comments so far

  1. Anonymous October 5th, 2011 12:45 am

    Well seeing as so many others are writing posts ranting about node.js, when is section 2 comming? ;)

  2. Taro October 5th, 2011 10:48 am

    Honestly, the event loop is a non-issue. You have to accept that, by nature, Node.js is a single-core beast. The V8 heap is not threadsafe — though there may be other threads running, none of them operate on Javascript objects.

    I remember waaaay back the recommended production deployment was 1 Node.js instance/core. With that in mind, you’ve always got something spinning on one of the cores. If it happens to be CPU-bound logic which is blocking an instance from handling other requests, then it’s your fault.

    But the gist of the worry was that _all_ logic is inherently CPU bound, and it’ll clog the event loop. But since each instance has it’s own CPU, that means you’re using all your machine’s power anyway. It’s just an issue with multitasking systems — they can only perform so much work — and not with Node.js.

    As far as the blog goes, I’ve uhh… switched to using Go and C exclusively at home, reserving Node.js and C++ for work-only use. Helps keep me sane enough :s

    Maybe I’ll blog about my ncurses Grooveshark client in a day or two (spoiler: their internal API is a clusterfuck).

  3. Anonymous October 5th, 2011 12:23 pm

    That would be pretty awesome.

  4. Anonymous December 1st, 2011 2:53 am

    Man, I’m still waiting :(

  5. Taro December 1st, 2011 10:38 am

    I’m such a lazy writer :(

  6. Anonymous February 7th, 2012 9:30 am

    I waited for you Taro, I waited for you….

  7. Anonymous May 10th, 2012 9:10 am

    dammit taro. always teasing

Leave a comment