The Most Elegant Piece of Code.

Recently, I've been thinking a lot about a particular piece of code and I can't stop. I think it's one of the most elegant functions I've ever seen. Without further ado, here it is in JavaScript.

const toArray = (value, ...rest) => (Array.isArray(value) ? value : [value, ...rest]);

As its name suggests, it's a helper function that casts any given value into an array. Why am I so obsessed with it? It ticks off probably every good software development practice checkbox.

Simple and Maintainable.

This function is short and simple. One of the main rules for building a maintainable software is you need to be able to understand what it does. One of the best ways to achieve that is to keep your modules short. The gold standard is to keep your functions under 25 lines of code. (I tend to exclude comments and string literals from this count.)

Readability and Testing.

When I started this article, I had a more readable of version of this function. However, this version is more robust and still quite easily understandable. You can use it in two ways – pass an array of values or an arbitrary number of arguments, and you will always get back an array.

If you can easily describe your code in human language, you can also easily test it. You can imagine how we would write tests for this function.

it('returns an unchanged array when passed an array', () => {
  expect(toArray(['foo'])).toEqual(['foo']);
});

it('returns an array containing passed arguments', () => {
  expect(toArray('foo', 'bar')).toEqual(['foo', 'bar']);
});

Building Robust APIs.

As you can see, a machine (compiler) would be definitely pleased with this code. Even more importantly, it offers great benefits to humans.

Building elegant APIs is hard. I've seen many examples in the past where I wanted a function to accept multiple parameters, but the support was missing. Or the opposite, I was forced to pass an array even when it contained only a single element.

The line above solves both of these issues. Suppose we have a select() function returning 1 or more rows by their id. Our users might also want to use this function in multiple ways.

select([1, 2]); // single array

select(1); // single non-array argument

select(1, 2); // multiple non-array arguments

How would we allow this kind of functionality? With our toArray() method, quite easily!

const select = (...rowIds) => fetchRows(toArray(...rowIds));

If you tend to compromise on quality because you think something would take too long to implement, hopefully this example convinced you that's not always the case.

Another way to use the toArray() method would be to create a wrapper.

const withArrayArgs = (fn) => (...args) => fn(toArray(...args));

This is even better because now you can start calling it on top of your existing APIs without changing their internals. Our original select() function would now look like this.

const select = withArrayArgs((rowIds) => fetchRows(rowIds));

Whenever possible, you shouldn't force people to use your APIs in a particular way. They will be grateful to you for making your APIs flexible and giving them control.

Limitations.

Our toArray() method works as long as we do not mix the array and non-array argument types.

toArray(['foo'], 'bar'); // returns ['foo'] instead of ['foo', 'bar']

toArray('bar', ['foo']); // returns ['bar', ['foo']] instead of ['bar', 'foo']

It also returns an unexpected result when called without any arguments.

toArray(); // returns [undefined] instead of []

The ability to handle such cases depends on your use case, so I decided to skip it in this article. I use even simpler version of this function and it meets my needs.

Conclusion.

Using this helper method in your APIs forces you, the developer, to adopt a consistent style. As a result, your APIs become more consistent and thus easier to use. It's only one line of code, but it has a profound effect on the quality of your code. As is often the case with quality products, the devil is in the detail.

Do you know any other little helper methods that positively impact the rest of the codebase? I would love to hear about them!



More aboutcode,guide,notes