Long anonymous chains of things

I recently had a conversation at work on possible coding advice, that quickly diverged into a matter of what I feel is code that speaks for itself vs. code that is ridden with comments because it can’t live on its own.

I tried to illustrate my point by defending some practices on clarity, which really are just a repackaging of common ideas from the world of “functional” programming — adopting recognizable patterns via higher-level functions, aiming for declarative code, centered around data transformations, etc. — in contrast with what I’d consider traditional imperative code — longer methods built around statements, more (and explicit) branching, mutable data and variable reassignments, etc.

My colleague then rightfully remarked that, while map, reduce, filter et al. can be used to make things clearer, they can be abused, and chaining a bunch of these can lead to monstrous spaghetti just as badly as more traditionally imperative code. Similarly, list comprehensions can be nice and concise, but don’t really scale. That got us to try to generalize, which led to my commenting:

I agree that “functional” code, in itself, isn’t immune to spaghettitis. I think the key here is really to keep functions small and as strongly specified as possible, so that 1) one can easily inspect them and understand what piece they represent in the larger chain of data transformations, and 2) anyone can think of them as black boxes (by trusting their specification; not specific to functional, but usually easier, especially with referential transparency) as they are called within a chain of manipulations elsewhere in the code.

In a way, maybe the root of all evil is just about anything we can call a “long anonymous chain of things”. So a long sequence of function compositions in one statement is bad, the same way a long sequence of rules within a list comprehension is bad, the same way a long sequence of statements within a procedure is bad. I added the term “anonymous” in there because I think the naming plays an essential role in the way we can break things down and grasp them. That is why my initial functional example [see below] defined isPopular separately, and it’s why a procedure might be defined in “paragraphs”, where each revolves around a variable or a helper.


var isPopular = ( post ) => post.popularity > 9000;
var topPosts = map( getPosts(), compose(
partial( takeWhile, isPopular ),
partial( sortBy, 'popularity' ) );
// versus
var topPosts = [];
var posts = getPosts();
posts = sortBy( 'popularity', posts );
forEach( posts, function( post ) {
// only keep popular posts
if ( post.popularity > 9000 ) {
topPosts.push( post );
}
} );