It struck me today that solving problems by writing code is a lot like solving a Rubik’s cube.
When you attempt to solve a Rubik’s cube, doing one side is pretty easy. It can often appear that you are making good progress: it’s already 1/6th complete! As you try to solve another side, you realise that in order to complete the second side, you have messed up the side you have already completed. These unintended consequences are very common when you write code. If you manage to complete 2 sides you feel like you are making significant progress – 1/3rd of the puzzle is now solved, you might think.
As you progress to the next side you become aware of the increasing complexity – how making changes in one place has ‘knock on’ effects in another. With each side you attempt, the difficulty of completing it becomes harder until you realise that you need to take a different approach.
At this point it becomes clear that what appeared to be fast progress at the beginning was deceptive – that, in fact, it wasn’t progress at all, because you were taking the wrong approach. The reality is that, if you had estimated how long it would take to complete the puzzle, an estimate made after one or two sides were complete would be wildly inaccurate – completing one side of the puzzle does not make the puzzle 1/6th complete.
The most common technique for solving a Rubik’s cube is to think of it in rows – starting with the top row, you work down each of the three rows, and finally complete the base. Of course the same principle applies here as with faces approach – the first row is the easiest to complete, and each successive row gets harder.
Solving problems in the right order is critical for success in a Rubik’s cube. If you have a block in the wrong place, but you decide to leave it and come back to it later, once the rest of the puzzle is complete, you will be setting yourself up to fail. Once the rest of the puzzle is complete, going back to fix the piece you skipped will break all of the work you have done since you made the decision to skip a step.
A cube can appear to be 90% solved, but still be a long way from completion, if there are critical issues with certain pieces in the wrong place. Conversely a cube can appear to be a long way from completion, but in fact be only a few steps away, given that you know the steps.
In fact, solving a Rubik’s cube can always be completed quickly: the solution for any combination will only be at most 20 steps. This is because computers don’t make changes that have unintended consequences – they are aware of the side effects of every change they make, so that all of the changes work together:
However humans don’t think like machines. We have to approach the problem in a way which minimises the complexity of each operation – we have to make small changes so we can understand what the implications of each one are – so we don’t mess up the other parts of the system.
By making small incremental changes we can see the implications of every change we make, and slowly chip away at the problem until it is solved.
This is a lot like writing code (especially code that isn’t orthoganal) – where changes made in one place can have unexpected consequences in another. There are two obvious outcomes of this:
- When working on complex systems, try to make your changes as small as possible, so that you can understand the consequence of every change. This will make it easier to see when things go wrong
- It’s really hard to estimate how long it will take to solve a problem you have never solved before.
The second point is really important, and worth elucidating on. With the Rubik’s cube, what appeared at first to be quick progress was in fact deceptive. Also, if critical parts of the problem were left until last, the effort to complete the remaining parts would still be large.
So it is when writing (and particularly modifying) code. A programmer that appears to be making good progress can soon find that they will need to make changes which will have implications beyond the scope of the system they are currently working in. The task which on the surface seemed simple suddenly becomes more complex.
Also, if a developer ignores one of the key problems with the system they are creating, hoping to find a solution later, this final issue can end up causing a big delay to a project. This may sound like a stupid mistake that a good developer would avoid, but the fact is that it is often not apparent which these critical pieces are, until a long way into a project.
It could be argued that once you have solved a few Rubik’s cubes you would start to get a sense of how long it would take you – you’d have a system in place and some experience to call on. This is true, but the fact is that the problems that developers tend to face are not ones they have solved before, so, unless they know the code they are working on well, each problem is like a new Rubik’s puzzle.
This is why I dislike estimating how long it will take to make changes to code. Maybe next time someone asks me I will give them a Rubik’s cube to solve.