Why KISS isn't easy
Let's talk about KISS, or “Keep It Simple, Stupid” as a principle for effective Software Engineering.
But before I go any further, just think a little about your favorite best practice when writing code. Is it DRY—Don't Repeat Yourself? Or are you more a YAGNI—You Aren't Gonna Need It—person? Do you follow SOLID principles? Or are you really sick and tired of all these abbreviations we have in IT, and just use common sense instead?
No matter what your style of coding is, it should follow one rule: Keep It Simple, Stupid!
So what should I tell you about KISS? It really just means you have to keep it simple. Simple code is less prone to bugs, and is easier to read and understand for you and the people who'll be working on the code in the future (including yourself). And now you're probably thinking you're already doing that. Duh, why would you write complex code? Maybe you even think of yourself as not being a good enough programmer to write complex code! Let me tell you why this is probably not the case.
Keeping things simple is, ironically, not simple! It requires abstract thinking. It requires knowledge of the domain you're working on. It requires knowledge of the code, the framework, and the language you're working with. It requires experience. In fact, let me present you this quote by Martin Fowler:
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
Think about it for a second—how much code have you seen that was easy to read, that was simple enough to understand? Probably not a lot. And who wrote that code? Were they beginners, hobbyists, seniors? Perhaps you've seen code from all those people—chances are, they were all complex monsters. Actually, if you looked at the code closely you'd probably find it littered with so-called “anti-patterns!” But here's the deal: the same goes for your code. You might understand it (now), but come back in a couple of years (or even months) and look at it again.
But why is so much code not simple? There just aren't a lot of people that possess the skills I just laid out: abstract thinking, knowledge of the domain, and technology. Some people just like to show off with complex code. Programmers often like to be “clever” and make code more complex than it has to be (because, let's be honest, using Reflection to get a properties value is much more fun than just getting the value).
Many programmers just get stuck in a certain train of thought, which isn't necessarily the right train of thought. In general, each programming task can be performed in countless ways. To fetch data from a database, you may write a stored procedure and call it using ADO.NET or Entity Framework (assuming you use .NET); or you may write the query in your code base using ADO.NET; or perhaps you're going to write a LINQ query with Entity Framework (or LINQ to SQL, or maybe you're still stuck with Typed Datasets).
Now, if we were going to count the ways in which you can write your SQL or LINQ query, you'd soon come to the conclusion that literally hundreds of ways exist to do something as simple as get data from a database. And that is usually just a small step of a bigger process, so count the different ways you can complete that process, multiply it by the different ways you can get data from a database, and the ways you can write this process probably gets close to an infinite number. Unless you're in Tenacious D, who just so happened to play the best song in the world, you're not going to write the best code in the world.
So how, then, can we write simple code? What I'm going to say now may be a little controversial, but I'm a big fan of counting the number of lines in my code base.
Actually, to be more precise, I count the number of statements in my code. A statement is an action a program takes—for example, declaring and/or assigning a variable, calling methods, looping through a collection, and so on. Most languages allow nesting statements or putting multiple statements on a single line, so lines of code obviously aren’t going to help much.
Bill Gates had similar feelings about counting lines:
Measuring programming progress by lines of code is like measuring aircraft building progress by weight.
Still, when I inherit a project, I like to take a look at the line count in files I need to work with. If the line count is thousands of lines of code, then the file, and likely the entire project, probably isn't KISS.
Before you go and write me hate mail on why number of statements is a bad measure, let me explain why I think it's actually pretty valid. First of all, if people read our code, there's simply going to be less to read. Less to read means less for them to understand. Less is more, right? Second, less statements means less debugging! If there's a bug in your code, you'd rather check ten statements than a hundred statements.
And last, but not least, each statement you write contains a potential bug. “That's stupid,” you might say, but think about it: a program written in hundred statements is less likely to contain bugs than the same program written with a thousand statements (assuming the latter just has a lot of unnecessary calculations and overhead).
Let's look at a real world (C#) example. Suppose we have a function that takes a string as input and returns a bool indicating whether the input is numeric (more precisely, an Int32). Here's what I think we can do. We can loop through the characters and use Char.IsDigit to check if every character is a digit. If we find one that isn't, we return false; otherwise, we return true. We can also use a Regular Expression to match the input with all numeric values. Last, we can use Int32.TryParse, which would actually give us the converted input as well, but we can discard it.
So here are the three functions:
I've actually seen a combination of looping through the characters and using RegEx to match each character with the [0-9] pattern… Obviously not KISS!
That already exposes a problem with the RegEx solution. RegEx is just difficult. It's great to solve some problems, but it already brings an initial complexity to the table that most programmers aren't familiar with.
In this case, we need to make sure the entire input is matched, and not just a part of the input. The programmer who used the loop with RegEx probably wasn't aware that the start and end of an input can be indicated in RegEx with ^ and $, so the [0-9] pattern would only work for single characters. Therefore, the RegEx solution is not KISS (even though it has the least statements).
That leaves the TryParse and IsDigit solutions. In this case, I'm simply going for the least statements rule, and IsDigit isn't KISS. Now I'll let you in on a little secret. The TryParse solution is the only one that isn't bugged. IsDigit and RegEx don't recognize the – character for negative values, but they do return true for 2147483648, which isn't an Int32 (overflow).
There's another problem with the RegEx and IsDigit examples. We're trying to reinvent the wheel. One rule that's certainly KISS is not to do that. We already have a function that checks if something is an Int32, so let's just use it. Obviously, the programmer who used a mix between the IsDigit and the RegEx solution didn't know about TryParse (or about Char.IsDigit), so it presents a problem if someone isn’t aware of this KISS solution.
When I'm faced with a challenge that isn't specific to my domain, I usually wonder if my problem was already solved. A quick Google usually does the trick. Googling for “c# check if string is numeric” will return all of the above solutions, so use your best judgement. If you find nothing, or none of the found solutions fit your requirements, Google some more.
Only after that should you be allowed to conjure up your own solutions. All of the “reinvented” code I've seen was bugged, including functions for DateTimes, security, and commonly-used algorithms. Those problems are widely known and can be solved by (standard) libraries written by people who are much more experienced with the material than you are, so use them. Here are some principles I follow when coding.
- Keep the scope of variables as small as possible. For example, don't declare a variable outside an if-block if it's only used within the if-block. The less declared variables you have at any given time, the less you have to worry about.
- Don't re-use variables (unless in a loop). I've seen code reusing a variable like i for temperature, height, and time. Just declare three variables, name them properly, and use them only for what they represent. Failing to notice a variable has a different meaning or use in other parts of the code can lead to some very strange bugs.
- Keep functions as small as possible and make sure they do just one thing. I'd rather have ten functions doing just one thing than one function doing ten things.
- Use proper naming for your variables.
- DRY: Don't Repeat Yourself. I've already mentioned it, but it's worth mentioning again. If you have duplicated code, try to make a function out of it.
- YAGNI: You Aren't Gonna Need It. Also mentioned before. Don't code more than you currently need; just write the simplest thing that would make your use case work.
- SOLID: This actually stands for a bunch of principles. Every object-oriented programmer should know them. It is perhaps equally important to know when NOT to use them! I will not explain them here, but SOLID on Wikipedia gives a nice overview.
Coincidentally, all these principles happen to be KISS. It's not easy to put them into practice, especially SOLID; but once you know them you can write better, maintainable, and above all simpler software. They might be open doors for you, but unfortunately there are some sloppy programmers out there who don't follow any of those rules. Don't be that programmer.
Designing with KISS
Let's also look a little at the bigger picture with an example. Say you're writing a program for a company that has a sales department, and now they want to show some statistics for each salesman.
In this application, you pick a salesman and the application shows the customers of this salesman, how many orders he's made, how much money he has earned you, maybe how much provision he's getting, and other similar data. What would be KISS?
I'm just going to “think out loud” in an abstract manner on how I would write this code. Maybe the user chooses a salesman and presses a button. On the button click, our (pseudo) code could look something like this:
Each statistic needs only one function call, so that's as few statements as possible.
Just a quick disclaimer: I wouldn't use var like that in production code, since it makes our code less readable!
Seems pretty KISS, right? Actually, it's not! Let's drill down a little deeper.
Judging from the method signatures, all of these functions need to acquire two pieces of information: the selected salesman and their respective statistic. Why would a function to get a statistic also get a salesman? That's not KISS—that's just weird and against our best practice of keeping functions small and assigning them a singular task.
So here's a better version of the code:
By just thinking about this a bit, we've eliminated repeating code before actually writing it in the first place. That certainly gets the overall number of statements way down. Additionally, the GetSalesManStatisticX methods are now simpler since they only do one thing (get the statistic).
As an added bonus, the methods just became reusable (just in case we ever need to get statistics for an additional salesman).
Notice that this isn't against YAGNI, since we actually need this code now. Would this do the trick? Maybe… But chances are, each of those methods has to do a roundtrip to the database. Could they be accessing the same data? Probably!
So we should combine some of those functions for two reasons. First, and actually most important: getting data from the database can take a while. Doing this multiple times can be a huge performance hit! So always try to get a piece of data just once.
Second, if our functions are getting the same data, we are probably repeating our code. So we now have a few options. I won't say one is better than the other, but here they are. How about having a function that gets the data and passes it as input to the functions?
Perhaps we could have a single method that calculates and returns all statistics at once.
Perhaps we can do all our calculations in the database.
Depending on the complexity of the calculations, I would probably go with the first or third method (although there are some pros and cons to putting these calculations in your database as well). The takeaway here is that all proposed solutions solve the problem of doing multiple round trips and fetching the same data.
This is actually a real-life example. A project I worked on used the first method I illustrated, just separate method calls that all get their own statistic. Things didn't end well when the data became bigger and more statistics were added. Lots of repeating code and redundant database calls. It's not hard to see why this method was chosen, however, as it seemed pretty KISS at first.
That also shows that being KISS is not easy. There is not one way to do things, and it takes knowledge and experience to pick a good method out of the infinite options for writing your code. I've found that a little common sense goes a long way.
So how can you become better at writing KISS code?
First of all, just read up on best practices! Here's a book I can recommend: Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin. If my recommendation isn't enough, John recommends it too in his book, Soft Skills! So be sure to read it. Also by Robert C. Martin is this series on SOLID principles, which you should really read and put into practice.
Second, be sure you Google for solutions before inventing them yourself. Googling effectively is a skill in itself. Sometimes I don't call myself a programmer, but a professional Googler! In these times, being a good Googler can really separate you from the flock. You can't know everything, but you can Google everything.
While we're on the subject, don't trust everything you read on the web. Even MSDN/TechNet has some articles that showcase bad practices! Keep using your common sense.
Just practice, practice, practice! Practice makes perfect, or so they say.
Then read code—lots of it. Find out what works and what doesn't.
And last, but not least, make sure your code is read too. Ask colleagues if they agree with your coding practices. If they don't understand your code, don’t assume they’re just too stupid to understand it. Ask them how you could improve it. If you don't trust your colleagues, or if you don't have any, write about your code, start a blog (using John's free blogging course), write for websites like CodeProject, or just ask a quick question on CodeProject or StackOverflow.
If you can apply KISS to your code, that certainly makes you stand out as a programmer. You can do something most people can't. That's gotta be worth something.
Good luck, and happy coding!