As developers, we pride ourselves in creating novel solutions to our problems, or by just simply reducing a few dozen lines of code into three or four. And although having clean code leads to a more readable code and, hopefully, a less buggy code, if we do it mindlessly and without planning, we are going to hit two common problems:

  1. We lose too much time trying to achieve simplicity, instead of focusing on delivering a product;
  2. When we do reach that “simplicity”, it is hard to read and understand, be it for other developers or our “future us”.

Does this mean that we cannot simplify our code or try to achieve clever decisions?

Not at all! On the contrary!

When we are searching for novel solutions, we usually discover new APIs and language features that will make our life’s easier in the long run. But to do that, we must start with the basics.

Imagine the following problem:

I want all even numbers that when doubled are divisible by 5

For this article, we will use the Dart programming language, and we’ll start by dividing (heh) the problem into several simple steps:

  1. We will need to filter a list for the even numbers only, we can use the where method for that
  2. Then, we will need to manipulate the numbers by doubling the filtered list, which can be done with the map method
  3. After, we will have to filter with where only the numbers divisible by 5
  4. And finally, we return the original value.

Translating this directly to code, we would have this function:

List<int> complexSolution(List<int> list) {
  return list
      .where((number) => number % 2 == 0)
      .map<int>((even) => even * 2)
      .where((number) => number % 5 == 0)
      .map<int>((number) => number ~/ 2)
      .toList();
}

I’d argue that this is a bit hard to read since we not only have 4 chained calls in a simple list but we also map and “un-map” a value.

If we were to go back to the basics, how could we achieve the same result but… simpler? Well, by using a for loop! Although it’s a bit strange, there are times where we realize that “I haven’t used a single for loop in over 6 months! How do we use them again?”, and right after that initial shock, we use it and as a result, we often simplify our code:

List<int> simpleSolution(List<int> list) {
  final result = <int>[];
  
  for (var item in list) {
    if (item % 2 != 0) {
      continue;
    }
    if (item * 2 % 5 == 0) {
      result.add(item);
    }
  }
  
  return result;
}

Looking at the above solution, we can analyze it very easily - we start a for loop with every item on the list, if it’s not even we skip directly to the next number with continue, and then with a simple condition we add the items to the list as needed.

Yes, it’s more lines of code, but we are not chaining different list operations or mapping values back and forth, we simply solve our problem with two conditions, making it easier to read for everyone, from a junior to a senior developer.

And this leads us to a very important question: we don’t know who will read our code in the future. It might be a junior who never saw the where function, or simply a very tired version of ourselves in the future, under huge pressure because we need to fix a bug that’s currently in production.

Which solution do you think makes it easier to:

  1. Look at the code at a glance and understand it;
  2. Make changes in the future, for example, a requirement change, in which we want numbers divisible by 3 instead of 5.

Opinions can differ on the first point, but on the second one, we can state that in the first solution we would have to change the calculation in two places, whereas in the simple solution we would only need to change one condition.

And the benefits of simplifying our solutions don’t stop here!

If we look at our simple solution, we realize that we don’t need to map any values, we just need to use 2 boolean conditions to get the values, which we will do in our where function:

List<int> simpleComplex(List<int> list) {
  return list
      .where((number) => number % 2 == 0 && (number * 2) % 5 ==0)
      .toList();
}

Voilá! We simplified our problem to only 1 list manipulation, and the only thing we need to do now is to read one boolean condition!

Simplifying, brick by brick

By taking the problem apart, and by using simple programming concepts such as boolean conditions and loops, we can virtually build anything. And by building it, it will be clearer for us how can we use other “building materials”, such as mapping, using design patterns, dividing our code in different functions and files, etc…

So in summary:

  1. When we try to simplify by using shiny operations and functions we can spend more time devising our solution, and we can come out of it with a difficult to read and difficult to change solution.
  2. By going back to operations such as if statements and for loops, we can simplify our problems - it will lead to more lines of code, BUT, it will make our code easier to read and easier to manipulate.
  3. With what we have learned by going back to the basics, we can change our “shiny operations” approach to make it cleaner and simpler.

Now I’m curious! Did you face a similar situation in the past? If so how did you (and/or your team) fix it? Tag me on Twitter (@gonpalma) and share it with the hashtag #FinalFormDev!