Create a gist now

Instantly share code, notes, and snippets.

style guide

High level style in javascript.

Opinions are like assholes, every one has got one.

This one is mine.

Punctuation: who cares?

Punctuation is a bikeshed. Put your semicolons, whitespace, and commas where you like them.

This post is concerned with higher-order style.

Be obvious

Don't do something complex just to make your api simpler. That will lead to confusing bugs, and necessitate reading your code, while also making reading your code harder. This will be frustrating for me, when I'm trying to use your module.

Example, avoid chaining DSLs.

This is bad:

  thing.when('something').then(doThing)

It's not really obvious how when relates to dothing. It's better to pass in related things together.

  thing.when('something', doThing)
  //or maybe
  thing({when: 'something', then: doThing})

Chaining where you simply return this is acceptable.

Be idiomatic, or not.

if possible, make your code follow the APIs in node core. That way, I already know how to use your module. I don't even have to read the documentation. This is ideal.

If you don't do this, you need documentation. But even worse, is if have merely a similar API, but does not behave exactly the same, I will misuse your code, get an error and be forced to read your code. This will frustrate me.

if your can't follow an idiomatic API precisely, do something completely different.

ALWAYS PASS ERR IN CALLBACK, (an event listener is not a callback, so this doesn't apply in that case)

If you have a function called createServer, it should return a server, and it should have a listen function. createServer should never start the server listening. That should never happen until I call listen.

a listen function that takes something other than a port, a hostname, and a callback (like in socket.io) should be called something else.

my favorite API from node is Stream (more on that later)

"all you need is lambdas" -- John Lennon

Functions are your most powerful weapon. In javascript you do not need fancy plugins. Allow users to extend of control your modules behaviour by passing in a simple function, with given arguments.

connect is a good example of this.

something only needs to be faster when it's slowing you down.

don't optimise things that aren't gonna be bottlenecks, example; it's completely acceptable to read configuration files synchronously. because you only do it once, and you need to do it before anything else happens in the application.

it's something you have to read every time you respond to an http request, then it must by async.

use flow control over control flow

streams in node are one of the rare occasions when doing something the fast way is actually easier. SO USE THEM. not since bash has streaming been introduced into a high level language as nicely as it is in node.

compare downloading a file one step at a time, and streaming it:

  //load the response entirely into memory
  request ('http://slow.com', function (err, doc) {
    //write the whole thing to disk
    fs.writeFile(dest, doc, function (err) {
      //done
    })
  })

now, stream it:

request('http://sweet.as') //read from the internet
  .pipe(fs.createWriteStream(pathToFile)) //write to disk as data arrives.
  .on('end', function () {
     //done
  })

the latter is preferred, the syntax is nicer, but more importantly, streams handle data in a smarter way.

they pass data along as it arrives, and can also tell the upstream to pause if they can't handle any more.

in the above example with the callbacks, the time it would take to complete is the time to request + the time to write to disk.

in the example with the streams, it will only take slightly longer than the time to request.

if the file is VERY LARGE this will be significant, also if it is VERY VERY LARGE it may not all fit in memory at once, but streaming will still work. the node.js team work very hard to make streams as fast as possible, and it's also easier, so use them.

if in doubt, modularize

node.js has the best package manager so far. This is not hyperbole. It's simple yet powerful. so split up every thing, this also makes it easier to test, and easier to extend.

I also like to separate layers,for example, if you are writing a service of some kind, write it to that you can test it entirely in memory, and then wrap it with layers that provide the network io.

conclusion

The style of a line is easy to change. But the style of a module is much more important. This is something that I think is truly worthy of debate, because it actually makes a significant difference.

what are your opinions on high-level style?

@devrim
devrim commented Apr 17, 2012

great post..

unfortunately, jquery has trained masses to use chaining, in weirdest ways possible. i see it spread to node modules, to basically everywhere JS goes. constructors are setters and getters, amongst 50 other things, pass boolean as first param it will do one thing, pass an object it will do 5 more things, pass a function it will callback that and return that=this.

thing({when: 'something', then: doThing}) is what we do every day. it just makes you smile each time you see a piece of code that doesn't force you to go to 5 other places to understand what it does, and how it does it.

@ThisIsMissEm

with then/when, that'd be actually following a Promise style pattern, which is quite well documented. A better example would be things like testing frameworks that use chaining. http://wiki.commonjs.org/wiki/Promises

@dominictarr
Owner

thank you for your comments.

Agreed on the weird jquery arguments. I read the jquery code once, on the inside it looked quite messy, from too much features creeping in, lots of options creates lots of edge cases.

The core idea of JQuery I really like actually -- you select a subset of objects (HTMLElements) and then you call methods to filter the set, or apply changes to the items. pick a simple api and then stick to it.

hmm. to be honest, I've never felt the need to use promises (but lets not get into that argument). but my example code does look like that. "well documented" is good, but not as good as "obvious".

@dominictarr
Owner

modules should expose simple node style async callbacks,
and if the use wishes they can use higher order programming to transform the module into a promise.

@axkibe
axkibe commented Apr 17, 2012

Fully agree, just would add, nothing is ever that obvious that a single line of documentation would hurt.

@dominictarr
Owner

A good approach is to think 'how will I document this?' when you are writing the code.
this is a good argument for idiomatic code, because you can just reuse other documentation.
just say X return a Stream and then link to http://nodejs.org/api/stream.html

@paulkernfeld

Hey @dominictarr, this is a nice guide. I'm curious: what's your perspective on documentation? For example, I see a lot of node projects by respected authors that don't include comments in the code. Is commenting regarded as a "band-aid," i.e. if your code is clear it shouldn't need comments?

@passcod
passcod commented Mar 3, 2016

@paulkernfeld comments !== documentation. For example, see the regenerator runtime source by Facebook: it has very useful longform comments to explain why some things are done, or what more obscure lines actually do:

  // Try/catch helper to minimize deoptimizations. Returns a completion
  // record like context.tryEntries[i].completion. This interface could
  // have been (and was previously) designed to take a closure to be
  // invoked without arguments, but in all the cases we care about we
  // already have an existing method we want to call, so there's no need
  // to create a new function object. We can even get away with assuming
  // the method takes exactly one argument, since that happens to be true
  // in every case, so we don't have to touch the arguments object. The
  // only additional allocation required is the completion record, which
  // has a stable shape and so hopefully should be cheap to allocate.
  function tryCatch(fn, obj, arg) {
    try {
      return { type: "normal", arg: fn.call(obj, arg) };
    } catch (err) {
      return { type: "throw", arg: err };
    }
  }

And see the browserify source by @substack, which has zero comments but does have extensive documentation (complemented by a massive handbook).

@dominictarr
Owner

I normally use comments when explaining a weird thing, or maybe marking something which was a painful lesson.
Other times I write comments while I'm figuring out what code i need to write (so it's stream of conciousnessy and I might just delete it later)

Comments are not documentation (and neither are tests)!

It was a long time since I wrote this, and so somethings would change...
I guess promises are okay now. well documented and consistent enough. (not that I use them though)

If I wrote this again, I think I'd talk about secondary conotations of code. When you read code, you gain meaning not just from what the code says, but also, what it doesn't say.

for example, named functions can be called from above or below.

callAFunction(namedFunction)

function namedFunction () {...}

if that is the only time namedFunction was used, then if it an anonymous function.

callAFunction (function () {...})

then that would be immediatly obvious. This applies to scoping also. If you do use a named function once, if you define it in the narrowest scope, then it's obvious that it's not used by any other parts of the code.

On the other hand, if you define functions at the outer most scope, then it's obvious they do not depend on variables inside those scopes.
(though, it's pretty easy to see what local and non local variables they have... but that should only be an issue on long functions, which are bad for other reasons)

Also, some people like to use named functions after their code. I really hate this, although a number of coders that I respect do it. Dependencies go at the top! a function that you wrote that you are gonna call later is a function. I'd gain more information by scanning past those functions than I would from reading the name. naming is hard, so try to avoid naming if you can.

@yoshuawuyts

Seems John Carmack thought the same:

This is going to be an unusual email – I want to talk about coding style. I’m not going to issue any mandates, but I want everyone to seriously consider some of these issues. I’m not talking about petty things like spaces around operators, bracing styles, or pointers by type or variable (although we probably should settle that one), but about larger organization of code. There aren’t any silver bullets, but months can be shaved off of a development project with a few percent improvement in productivity.

@vajahath

Nice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment