Give Your Code Room to Breathe

Aug 21, 2024

One very simple way to improve the readability of your code is to add blank lines throughout your functions. As an example, let's take this function that I grabbed from the axios package with all of the blank lines removed:

export default function fromDataURI(uri, asBlob, options) {
  const _Blob = options && options.Blob || platform.classes.Blob;
  const protocol = parseProtocol(uri);
  if (asBlob === undefined && _Blob) {
    asBlob = true;
  }
  if (protocol === 'data') {
    uri = protocol.length ? uri.slice(protocol.length + 1) : uri;
    const match = DATA_URL_PATTERN.exec(uri);
    if (!match) {
      throw new AxiosError('Invalid URL', AxiosError.ERR_INVALID_URL);
    }
    const mime = match[1];
    const isBase64 = match[2];
    const body = match[3];
    const buffer = Buffer.from(decodeURIComponent(body), isBase64 ? 'base64' : 'utf8');
    if (asBlob) {
      if (!_Blob) {
        throw new AxiosError('Blob is not supported', AxiosError.ERR_NOT_SUPPORT);
      }
      return new _Blob([buffer], {type: mime});
    }
    return buffer;
  }
  throw new AxiosError('Unsupported protocol ' + protocol, AxiosError.ERR_NOT_SUPPORT);
}

When I see a function like this, my eyes immediately glaze over. It feels overwhelming to try to understand. With enough time and focus I can eventually understand it, but the goal should be to make our code as easy to read as possible.

Where do I put the blank lines?

You certainly don't want to add blank lines everywhere, or in arbitrary spots. The key is to look at the function and identify units of logic within the function. We can then group those together and leave blank lines in between.

Here's the function with blank lines added back in:

export default function fromDataURI(uri, asBlob, options) {
  const _Blob = options && options.Blob || platform.classes.Blob;
  const protocol = parseProtocol(uri);

  if (asBlob === undefined && _Blob) {
    asBlob = true;
  }

  if (protocol === 'data') {
    uri = protocol.length ? uri.slice(protocol.length + 1) : uri;

    const match = DATA_URL_PATTERN.exec(uri);

    if (!match) {
      throw new AxiosError('Invalid URL', AxiosError.ERR_INVALID_URL);
    }

    const mime = match[1];
    const isBase64 = match[2];
    const body = match[3];
    const buffer = Buffer.from(decodeURIComponent(body), isBase64 ? 'base64' : 'utf8');

    if (asBlob) {
      if (!_Blob) {
        throw new AxiosError('Blob is not supported', AxiosError.ERR_NOT_SUPPORT);
      }

      return new _Blob([buffer], {type: mime});
    }

    return buffer;
  }

  throw new AxiosError('Unsupported protocol ' + protocol, AxiosError.ERR_NOT_SUPPORT);
}

What a different blank lines can make! This function is so much easier to read. We have grouped our variable definitions at the top of the function as well as in the middle, and also surrounded the if statements with blank lines.

It may not always be obvious where a blank line should go, and there is no hard and fast rule. But as you write your functions, be on the look out for units of logic within your functions that you can group together and observe how much easier to read your functions become.