Aiming Simplicity
When thinking about code, imagine there are two types of programming problems: Easy and Hard.
Easy problems are straightforward, like finding the largest and smallest values in an array or inserting a node into a binary tree. Hard problems are more challenging, such as implementing memory allocation or parsing a scripting language.
These two extremes are just a starting point, and most problems fall in between. You might need to write more code to solve a Hard problem, and the code may be longer and more complex.
Think of solutions as either Simple or Complicated. Simple solutions are short and easy to understand, while Complicated solutions are long and difficult.
With these two categories, you can create a matrix with four types of code :
Easy Problem | Hard Problem | |
---|---|---|
Simple | Expected | Aspirational |
Complicated | Really, really bad | Accepted |
We can see that there are Simple solutions for Easy problems and Complicated solutions for Hard problems. However, it's easy to write Complicated solutions for Easy problems and possible to write Simple solutions for Hard problems.
With this in mind, it's clear where the next Rule is heading: find solutions that are as simple as possible. Let's explore some examples!
Easy Problem, Simple Solution
It's easy to turn a simple algorithm into complicated code. Here are three common ways to do it:
- Overburden with abstractions: You can take a simple algorithm and wrap it in layers of complexity. For example:
This code is the same as the simple algorithm, but it's much harder to understand.
- Solve a more general problem: Sometimes, people try to solve a more general problem than what's needed, making code harder to read. For instance:
This code solves a more general problem than just finding the minimum and maximum values, but it's also harder to understand.
- Choose the wrong algorithm: It's possible to misjudge the best approach, making the code unnecessarily complex. For example:
In this case, the simple algorithm is obvious, but it's easy to misunderstand and create more complicated code.
In summary, these are three common ways to make code more complicated than necessary:
- using too much abstraction,
- adding unnecessary generality
- choosing the wrong algorithm.
Easy Problem, Complicated Solutions
Complicated code is often a result of taking a simple algorithm and wrapping it in extra layers of abstraction, like this example:
This code is the same algorithm, but harder to follow due to more lines and complexity. Meanwhile, the original example was straightforward and easy to understand.
There are other ways to complicate code, such as:
- Overly general solutions that solve a broader problem instead of a specific one.
- Choosing the wrong algorithm in the first place.
These approaches often lead to code that's harder to read and maintain. Instead, aim for simplicity and focus on solving the specific problem at hand.
The Cost of Complexity
Complexity has a real cost. Writing complicated code takes longer to write and debug, and it's harder for others to understand. Our simple solution avoids these issues.
A good programmer solves easy problems with simple solutions. solving hard problems and writing simple solutions for easy ones. If you can't do both, we're not interested.
Complicated solutions to easy problems harm the team. They take more time, introduce bugs, and are frustrating to work with. We can't afford that complexity.
The Three Kinds of Programmers
There are four types of programmers: mediocre, good, and great. Given an easy problem, do you write a simple or complicated solution? Given a hard problem, is your solution simple or complicated?
There is no one who writes simple solutions to hard problems and complicated solutions to easy ones. This leaves us with three types of programmers.
Type of Programmer | Easy Problem | Hard Problem |
---|---|---|
Mediocre | Complicated | Complicated |
Good | Simple | Complicated |
Great | Simple | Simple |
The difference between mediocre and good programmers is that good programmers write simple solutions to easy problems. The difference between good and great programmers is that great programmers write simple solutions to hard problems as well.
Eventually, problems become hard enough that there are no simple solutions. The best measure of a programmer is how far they can go before their solutions become complicated. The farther they can go and the harder the problems they can solve with simple solutions, the better they are.
Alternatively, a great programmer can recognize when a hard problem is actually easy if looked at from the right angle.
Hard Problem, Somewhat Complicated Solutions
The problem is to check if a sequence from a set of letters appears consecutively in a search string. For example, with the set ""abc"", the function should return true for ""cabbage"" and ""abacus"", but false for ""scramble"" and ""brackish"".
The initial approach is to generate all permutations of the set and check if any appear in the string. However, this becomes impractical for larger sets due to the vast number of permutations.
The first attempt is to generate permutations recursively, adding each character to all permutations of the remaining characters. This seems to work, but quickly becomes unusable for larger sets.
A naive optimization attempt removes duplicate permutations, but this doesn't address the core issue. The problem cannot be optimized out, and the code remains unworkable unless the sets are small or mostly duplicated.
Hard Problem, Somewhat Complicated Solution
To fix this problem, we need to change our approach. Instead of generating all permutations, we'll check each substring of the search string to see if it matches the permute string.
This works, but the nested loops can be hard to follow. You might be worried about performance, but unless the permute string is very long, it's not a problem.
The real issue is that the solution is more complicated than it needs to be. A better programmer might try to optimize it by avoiding the nested loops, but that's unnecessary. The solution works fine as it is.
A more typical solution might involve complex counting and hashing, but that's just making things harder than they need to be.
Hard Problem, Simple Solution
What sets great programmers apart from good ones is finding solutions that are easy to read and understand.
In this case, our code checks each substring of the search string to see if it matches a permutation of the permute string. But we can simplify it.
Standardize the order of the letters in the permute string, and then do the same for each substring. Now you can just compare the two strings.
This approach doesn't change the underlying algorithm, but it makes it much easier to understand. A great programmer finds simple and clear solutions like this one.