Use RSpec to improve performance via precomputation

Suppose that we have a Ruby class called SomeClass, and SomeClass has an array class constant called SOME_ARRAY. SomeClass also has a method called same_elements? that checks if an input array has the same elements as SOME_ARRAY without regard to ordering. We may be tempted to write code that looks like this:

This code works, but it’s an inefficient solution. We sort SOME_ARRAY every time same_elements? is called, even though it’s a constant that we defined ourselves!

We could define SOME_ARRAY as ['foo', 'bar', 'car'].sort, and that gets us most of the way to improved performance. We only expect Rails to load our class definition for SomeClass once when we start our application, so even if we’re creating many instances of SomeClass (eg. once per request to a REST endpoint), we’re still only calling .sort on SOME_ARRAY once.

However, if we want to be even more efficient with our compute power at runtime, we can simply define SOME_ARRAY to be sorted when we write our code, which lets us avoid calling .sort when we load the class definition.

This revised code has even fewer calls to .sort than when we defined SOME_ARRAY as as ['foo', 'bar', 'car'].sort. However, this introduces a new problem into our code: if we ever rewrite or modify SOME_ARRAY in the future, we need to ensure that that constant remains sorted.

For example, a future commit could change SOME_ARRAY to ['new element', 'bar', 'car', 'foo'], which would cause a bug in our same_elements? method. Hopefully our code review process and tests on same_elements? would stop us from introducing that bug.

However, through RSpec, we can actually enforce that SOME_ARRAY remains sorted. We introduce a test like so into our SomeClass test suite:

This ensures that SOME_ARRAY remains ordered when we introduce new elements to it. By precomputing the sorted order of SOME_ARRAY in our testing and continuous integration environments, we’re able to do fewer .sort computations at runtime in our application.

Note that we should only use this method when there’s no impact to SOME_ARRAY‘s readability. If SOME_ARRAY is defined as ['foo', 'bar', 'car'] (ie. random elements where we don’t care about the ordering), making its definition alphabetical in the source code makes sense. Using this method also makes sense when SOME_ARRAY is something we already expect to be alphabetized (eg. ['alpha', 'bravo', 'charlie']). However, if we expect some other sort of non-alphabetized ordering of SOME_ARRAY, then making our code less readable in order to save compute power would be the wrong tradeoff. For example, redefining ['one', 'two, 'three'] to the sorted ['one', three', 'two'] makes our code less readable, which isn’t worth the slight performance gains.

Start your journey towards writing better software, and watch this space for new content.