Simple changes might not lead to simple solutions

Posted on 10. Jan, 2011 by mauricioaniche in agile, design, tdd

The process of delivering a new feature is consisted in implementing or fixing something that the current state is not capable of. Given the set of all problems that your software solves at this moment, there are many different ways to solve them. All of those possible solutions are valid and would end up in working software, but some are cleaner, easier to mantain and simpler than others.

The image below presents a situation where a new feature needs to be implemented because the software does not solve the current problem the company is facing:

a) red: represents all possible systems that do not solve the problem;
b) yellow: the current situation of our software;
c) green: possible solutions for both the previous solved problems and the current one.

m1

Possible solutions for a problem

However, inside the possible solutions, there are those that are simple to be done. For example, writing an “if” or simply do a copy and paste some piece of code. This is a little step, a simple change, a baby step. The graphic below represents simple changes that solve the problem in dark green:

m2

Simple changes

In addition, there are the simpler solutions for the problem. The simpler solution might be to extract a class or to make use of a library that does the logic. The diagram below represents the simpler solutions, in blue:

m3

Simple solutions

It is possible to make some small changes, baby steps, that will put our system back in the track: inside the set of situations where a simple change solves the problem. But the simplest solution to our problem might be something completely different. It might not envolve the simplest change, it might even be on the different direction; doing small simple changes might lead us to a increasingly bigger technical debt. A typical 90′s example of that is the copy and paste solution, that will solve a lot of problems – and even professional developers stick to it in some situations – but will increase our debt over time, going in the wrong direction.

Even worse, the blind adoption of baby steps or simple changes with no practices to improve the quality of the design may take the code to a situation where it would be impossible to go back to a simple solution: your system becomes a big ball of mud. This is the worst situation: baby steps are not capable to keep things simple anymore, a typical situation where clients will say “we need to restart this project even before it was released”.

m4

The pure use of baby steps do not assure quality. Too much simple steps may lead your system to negative situations.

In some situations, baby step may imply in one of the best solutions. But, to make it possible, the developer must have a deep knowledge in the technology, in the business domain, software design and programming language, which is not always true.

m5

Baby step by a developer with strong knowledge in the technology, language, business domain and system.

As it is hard to make a smooth step-by-step and come to a simpler solution, developers require extra techniques, such as refactoring. Each simplification step through refactoring puts the code closer to the ideal system: the simplest thing that solve the set of required problems.

m6

Refactoring that takes the code closer to the best solution.

With continuous refactoring, each step programmers take to implement a new feature keeps the code still close to simple solutions. Even more important than doing tiny steps (baby steps) is to walk in direction that takes to the better solution (and not only to the simplest change). In order to do that, programmers can not forget to refactor. And not only low-level refactoring (i.e. renaming variables, extracting methods, etc), but high-level refactoring (e.g. creating new abstractions).

The following piece of code is responsible for calculating the salary of a employee. It depends on the employee’s position on the company.

public class SalaryCalculator {

  public SalaryCalculator(FiscalRules fiscalRules, ...) {
    // ...
  }

  public double calculate(Employee f) {
    if(f.getPosition() == Position.SALESMAN) {
      // calculate salesman salary using fiscal rules, etc.
    }

    if(f.getPosition() == Position.MANAGER) {
      // calculate manager salary using fiscal rules, etc
    }
  }
}

Suppose that the company has a new position. The software should calculate the salary for this new employee as well. The simplest code that solve the problem would be:

public class SalaryCalculator {

  public SalaryCalculator(FiscalRules fiscalRules, ...) {
    // ...
  }

  public double calculate(Employee f) {
    if(f.getPosition() == Position.SALESMAN) {
      // calculate salesman salary using fiscal rules, etc.
    }

    if(f.getPosition() == Position.MANAGER) {
      // calculate manager salary using fiscal rules, etc
    }

    if(f.getPosition() == Position.MARKETING) {
      // calculate marketing salary using fiscal rules, etc
    }
  }
}

However, this is not the best solution. Moreover, the code gets more and more far from the best solution after any new “if” inserted in the code. Design gets more rigid. And worse: it makes the code harder to refactor: even the second “if” wasn’t the simplest solution.

When implementing a new feature, the programmer should check if the current design enables him to move his system to the next stage of simplest solutions, which might not be the simplest step. If the current design already provides an easy way to implement it, go ahead. Otherwise, the developer should refactor the design in order to make the implementation of this feature as easy as possible.

Steps do not need to be the smallest as possible. If the shortest step is a baby one, developers are free to do children steps when those will improve the software in question. Every design decision requires more and more knowledge out the developer, the small step and refactor techniques are one way to get good feedback to a software designer, allowing him to make good decisions, but still limited to his reachings.

In the example above, one solution would be to create an Strategy (GoF) to calculate the right salary for each position. And each position would be responsible to give the right algorithm:

public class SalaryCalculator {

  public SalaryCalculator(FiscalRules fiscalRules, ...) {
    // ...
  }

  public double calculate(Employee f) {
    SalaryAlgorithm calc = f.getPosition().getCalcAlgorithm(fiscalRules);
    return calc.calculate();
  }
}

It is hard to say if this is the best solution. Yet, it is much better than the ones before. Now, if the programmer needed to implement another salary algorithm, that would be much easier. Now, the programmer can go back to small steps, as the current implementation is on a safe place.

All this post may be summarized in one phrase that came up during a conversation between Guilherme Silveira and Mauricio Aniche:

The simplest change that allows you to solve a problem is not necessarily the simplest solution in the long run.

Tags: , , , , , , , ,

One Response to “Simple changes might not lead to simple solutions”

  1. J. B. Rainsberger

    10. Jan, 2011

    The simplest change is not always the easiest change. See http://bit.ly/eIoMfR for details.

Leave a Reply