Sunday, April 17, 2011

K.I.S.S. part II

This post is about keeping it simple on the solution side of things:

Now it's easy for me to say "don't make shit complicated". I mean some shit is complicated by nature, and the only thing that's left to do is get up to speed on the details until it becomes clear. In addition, due to the exploratory nature of software development, often shit only becomes simple after you've done it the complicated way. Our understanding of the problem may grow as time goes on, and based on the improved understanding, the code may start looking clearer, and typically more compact.

I think it was Mel Gibson who once said about acting "it takes a lot of work to make it look effortless". As a refactoraholic, I can testify to the fact that "it takes a lot of work to make code look easy". A person looking at simply written code might be tempted to say "oh yeah, I could have thought of it myself". Just like a good textbook that does a good job of explaining something, might get you quicker to the eureka moment, so will compact, loosely coupled code, with good comments and variable names that make sense get you to find logical errors and bugs, as well as make adding new features faster.

After refactoring I often stumble on some "obvious" logical errors in the code. It can look like a dumb mistake in the logic, but it only became obvious after 2-3 rounds of refactoring. Previously this mistake may have been burried so deep, it would have been much more difficult to "see".

K.I.S.S.

As I look at the experiences I've had handling difficult code written by other people, some patterns seem to emerge. One of the most persistent patterns that seem to be causing most of the software maintenance issues I've encountered is people making shit too fucking complicated! And by that I mean people not following the principle "Keep it simple, stupid" (aka, K.I.S.S.).

There is one way of making shit complicated that I am attacking and that is taking care of unnecessary use cases. In my opinion, the code that is most difficult to maintain is code, which has a lot of what-ifs in it, a lot of just in case handling. This coding strategy is the root of all evil, and together with premature optimization forms the two black pillars of death. So here I'm going to try to make a distinction between:
 (1) unnecessary code to solve a necessary problem, and
 (2) code to solve an unnecessary problem (the code in this case is also, by definition unnecessary).

The first case is about proposing a solution that's unnecessarily difficult, the second case is about proposing a solution that's not necessary at all. The first case is about overcomplicating the solution space, while the second is about overcomplicating the problem space.


What if the hero in our game is hanging down from a helicopter, and a bullet enters his left eye? Should we simulate the eye movement on the right eye as the bullet gets closer? What if one of his eyes gets streamed out in the process? What if there's an eye-patch on the left eye? Should we simulate it with cloth physics?
OK OK, stop right there. How about let's consider if this combination of circumstances needs to be handled at all?!

Life of a coder is difficult enough as it is, we shouldn't have to solve imaginary, or potential problems before solving real ones. One of the most obvious signs that code is solving an imaginary problem, is large amounts of boiler-plate code, a lot of methods and files that seem to be doing very little, as if the original author of the code had something grander in mind, and put in some code just in case. What's makes matters worse is that the code for solving a non-existing problem is living right alongside the code that solves an actual problem. Then, the reader of the code in question has to not only understand how this code solves an actual problem, but also what other than the actual problem is this code trying to solve?

Continued in this post: http://refactoraholic.blogspot.com/2011/04/kiss-part-ii.html