Inline programming

I’ve never seen it called this way but, what the hell, what kind of world would it be if a man wasn’t free to make up his own jargon. So here’s an example of what I like to call inline programming:

fetch('/posts')
  .then((response) => {
    response.json()
      .then((data) => {
        const posts = data.results;
        posts.forEach((post) => {
          if (post.comments.length > 0) {
            renderComments(post.comments);
          }
        });
      });
      .catch((error) => {
        alert('Oh not this again!');
      });
  });
  .catch((error) => {
    if (error.errorCode === 42) {
      alert('Aw, snap! The server is on fire!');
    } else {
      // Log error but don't tell the user (shhh...)
      console.error(error);
    }
  });

The problem

This snippet sucks bad. It’s hard to read, it’s hard to understand, it feels like a Jenga tower and a minimal change might lead to unpredictable disasters.

Now it’s 2017. We have things like distributed containerized fault-tolerant progressive componentized mobile-first apps. But people still write this sort of crap.

Don’t get me wrong: I might have done this myself as well. But one thing is to make mistakes and be oblivious to them, another (much better) is to realize when something is wrong and (sooner or later) fix it, or at least acknowledge it with a nice TODO comment.

This is closely related to the use of abstractions. “Code Complete” (Steve McConnell) describes the process in the section “Form Consistent Abstractions”, Chapter 5. The author’s definition of abstraction is:

Abstraction is the ability to engage with a concept while safely ignoring some of its details—handling different details at different levels.

That “safely” is the key. Having abstractions that expose too much (or too little) is useless. In the first case the cognitive load is still heavy, in the second case you take away flexibility and prevent the abstraction’s user from adapting it to their needs, which often leads to that same developer inventing a new abstraction, completely defeating the purpose.

Here’s an example of “too much abstraction”:

function fetchPosts() {
  fetch('/posts')
  .then((response) => {
    response.json()
      .then((data) => {
        const posts = data.results;
        posts.forEach((post) => {
          if (post.comments.length > 0) {
            renderComments(post.comments);
          }
        });
      });
      .catch((error) => {
        alert('Oh not this again!');
      });
  });
  .catch((error) => {
    if (error.errorCode === 42) {
      alert('Aw, snap! The server is on fire!');
    } else {
      // Log error but don't tell the user (shhh...)
      console.error(error);
    }
  });
}

fetchPosts();

If I were to change the URL used to fetch such posts, or anything else for that matter, I’d have to dig into the “abstraction” (here in the form of a function) and change its implementation. Ouch. I can’t “safely ignore” how fetchPosts is implemented.

We can do better

Let’s start refactoring the snippet. An inside-out approach works perfectly here:

function hasComments(post) {
  return post.comments.length > 0;
}

function renderPostComments(post) {
  if (hasComments(post)) {
    renderComments(post.comments);
  }
}

function handleFetchError(error) {
  if (error.errorCode === 42) {
    alert('Aw, snap! The server is on fire!');
  } else {
    // Log error but don't tell the user (shhh...)
    console.error(error);
  }
}

function handleJsonError(error) {
  alert('Oh not this again!');
}

fetch('/posts')
  .then((response) => {
    response.json()
      .then((data) => {
        const posts = data.results;
        posts.forEach((post) => {
          renderPostComments(post);
        });
      });
      .catch(handleJsonError);
  });
  .catch(handleFetchError);

Now we can move the error handling functions and the post utility functions in separate modules:

import {renderPostComments} from './post-utils';
import {handleFetchError, handleJsonError} from './error-handlers';

fetch('/posts')
  .then((response) => {
    response.json()
      .then((data) => {
        const posts = data.results;
        posts.forEach((post) => {
          renderPostComments(post);
        });
      });
      .catch(handleJsonError);
  });
  .catch(handleFetchError);

Nice cleanup. Is this the best way to abstract all this? Of course not! But it’s a start. Let’s continue:

import {renderPostComments} from './post-utils';
import {handleFetchError, handleJsonError} from './error-handlers';

function handlePosts(posts) {
  posts.forEach((post) => {
    renderPostComments(post);
  });
}

fetch('/posts')
  .then((response) => {
    response.json()
      .then((data) => handlePosts(data.results));
      .catch(handleJsonError);
  });
  .catch(handleFetchError);

Notice how handlePosts is not aware of how the response looks like. It only knows about posts and delegates the rest to renderPostComments.

But:

(data) => handlePosts(data.results)

although perfect for bragging with your friends who write Java, is far from being meaningful. Let’s fix that, this time by extracting that, and more, into a meaningful function:

import {handlePosts} from './post-utils';
import {handleFetchError, handleJsonError} from './error-handlers';

function handleResponse(response) {
  response.json()
    .then((data) => handlePosts(data.results));
    .catch(handleJsonError);
}

fetch('/posts')
  .then(handleResponse)
  .catch(handleFetchError);

I did not create yet another function for extracting results from data because at some point it’s better to use (but not abuse) the language rather than wrap everything into functions because it’s this year’s trend.

Our final result looks like this:

import {handleFetchError} from './error-handlers';
import {handleResponse} from './post-api-utils';

fetch('/posts').then(handleResponse).catch(handleFetchError);

Now, look at that! Some might argue that we went too far and that we might be a bit more explicit but that’s ok. As professionals we are all entitled to having our own opinion, as long as it makes our life (and, more important, the life of our peers) easier. This is just an example and it should not be treated any other way.

How to organize your modules is an entirely different beast. What did we accomplish by simply creating meaningful functions and distributing them among different modules? Better readibility (I can focus on each function separately instead of being distracted by the whole), reusability, better semantics (i.e. it “reads like English” and not like a language closer to the bare/virtual metal).

I dare even an inexperienced developer to say he’s “confused” by a function called handleResponse or renderPostComments. The trick here is: you don’t have to know Javascript to understand what this program is doing. This isn’t always possible but it’s a good predictor of quality:

How much can someone who doesn’t know the language understand about my program?

Hopefully 2017 will be the year “inline programming” is finally seen as disgusting and deplorable as using something like goto or not indenting properly (Brrr).

Notes

  • I willingly avoided creating a class for posts to keep things simple, but it wouldn’t be a bad idea (as long as you don’t recreate an ORM).
  • I didn’t think much about the organization of the modules, the purpose here is not to teach how to architect your codebase, rather how to name things and separate concerns (more or less effectively).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s