As software is one of the most important issues in our era, writing good robust programs is essential. This article is an in-depth essay focused on Object Oriented software and large projects. Everything said here, though, scales well to good directives for small projects as well.
Our time is dominated by software. There is basically software everywhere around us; most of the object you can see right now around you, have something to do with software, probably because they were created using some sort of machine. Given the importance of software nowadays, I just have to find bugs unacceptable. Of course you might argue that a small and rare bug is a minor software won’t harm anyone, and is not nearly as important as a bug that could affect the software of an airplane, and I’m going to agree with that. But as time goes by, everything has to be going towards perfection, and current trends about software seem to be going nowhere: there were bugs in software 30 years ago, and there are today. There was a time, in the beginning, where scientists thought that it would be relatively easy to write bug free programs right away, but then they realized pretty soon that it wasn’t quite so. After all, software is written by us human beings, and we are doomed to make mistakes or omissions. The point of this article is not that software should be always bug free, but that we, coders, should always get them to the minimum, and here I’m going to present some ways to deal with programming in general.
One huge problem, as I’ve faced quite often, is that as a program grows in size and dependencies, its developers start losing trace of its components, get further away from the big picture, and ease the introduction of bugs. Note, I’m not talking here about bugs caused by a single human error that can be labeled as a cheap error by anyone who would look at the code. I’m talking about the sort of nasty bugs that nobody can spot right away with a glance at the code. I’m talking about system wide bugs, usually emerging as a result of hardly related subsystems of the program. Usually connections between dependencies and libraries.
Anyway, the path to write bug free code, is the one you step when you write robust code. What do I mean by that? Robust code has some features:
- Well designed
- Neat and tidy
- Well named
- Well commented
- Well tested
- It never segfaults
As a result of some of these, robust code is also:
- Lasting in time
Having already talked about this somewhere else, I’ll be brief on this section. Writing a complex program, a program made of hundreds of thousands lines of code, is a damn complicated thing: it takes many people and a lot of time. Usually, the more people you involve in the project, the less robust code you’ll get in the end. People will use different conventions and different styles. For this reason, not only it’s crucial to hire the right developers, but it’s essential to have a very strict and detailed specification of the project. Programming is a creative work, no doubt, and coders need to have freedom so they can breathe. A constrained coder is a chained coder, hence a dead coder and a threat to the quality of the end product. But, in spite of how much we care for the freedom and openness of initiative from the developers, we have to be aware that loosing control means lowering the quality. A large project must be designed thoroughly and carefully, in every single details. Even though programmers love freedom, most of them also love exhaustive documentation. If you want to make a good coder happy, and get the best out of him, flood him with docs and specs. Nothing pisses off the good coder as the lack of documentation: it tears his motivation apart. “Why should I start to read their minds and run by guesses” - he thinks, “when they didn’t even get the time to write good specs?”. Furthermore, a project without good specs looks superficial, destined to failure and without a future. A very good coder is hardly going to stay in a company that doesn’t make good design for the projects. He will think that it’s a loser company, and start looking around.
But what does good design mean? A good design is:
- Non redundant
- Non contradictory
- Easy to understand
- Related 1:1 to the implementation
We want to cover every possible outcome in our specification, let be them exhaustive so that nothing will be left to case. We don’t want to repeat the same information more than once, and be redundant for several reasons, e.g. information should be retrievable in exactly one place, and it would ease up contradictions. Documentation should be for the developers, i.e. written in the most straightforward way for the right audience: simplicity of language and straightforwardness of tables and schemes will spare some curses from the developers. Furthermore, as a specification is just a way to put a program in words before it’s written, developers should be easily able to translate what they see on paper to code. Think about a shopping list: when I get one, I just go to the shop and take care of translating each item on the list to a physical item in my shopping cart. Direct and easy.
Neat and tidy
A good definition of neat is: in a pleasingly orderly and clean condition. How does that apply to software? What is neat software? One nice word that I like in that definition is “pleasingly”. Neat software pleases the eye and the mind. Don’t want to be cocky here, but neat software is something written by a good programmer, and will be appreciated by another good programmer. If somebody known as a good programmer points at some software and says “That’s neat” and you find yourself looking at it and replying “Huh? That’s just code”, I’m sorry but chances are that you are not a good programmer. A good programmer appreciates the beauty of some code, both on a small scale and on a large scale. Neatness of software on a small scale means that you’re able to look at one function and appreciate the simplicity of it. Neat pieces of code are easily readable and use good name conventions. Please read this article if you want to know more about good code on a small scale. Neat code on a larger scale, on the other hand, means neat integration between components and subsystem of a project. A bad integration would mean, e.g., having a project-wide global variable that points to a certain subsystem, and using it everywhere in the project. Or having two subsystems that, in a messed and intertwined way, mutually call each other’s methods violating several layers of abstraction. Proving what neat code is, turns up to be very difficult. It’s a bit like the opposite of what happens with common logic: if I want to prove you that, say, lions exist, I can just go to Africa, pick one and show it to you, then say “That’s a lion, ergo lions exist”. But how can I prove that unicorns or dragon don’t exist? You probably agree that it’s much more difficult. It’s just the opposite with neat code. I can show you bad code, and you will easily agree that it’s bad. But looking at neat code doesn’t it prove it neat right away. It takes probably years and years of experience, writing a lot of code and reading a lot.
This topic has already been discussed here, but repetuta juvant. As code is managed by possibly dozens or more people, being understood is an important key to increase robustness of the code. Writing robust code also means writing code that will easily stay robust when other people will modify of expand it, unless they have no clue, of course. The most your code is understood by others, the most likely they will not break your ideas, and keep the code robust. There are several ways of making own code easily understood, and having a good, consistent and solid naming convention is one of them. Of course, as discussed later, code needs to be well documented also.
I know, I know. Everybody says that you should comment your code. That’s what I say and that’s what I’ve been told. Still I’m now comment my own code enough as I should. Before you can then tell me “Who are you, then, to tell me to comment my code, if you don’t do it enough with yours?” let me remind you that we learn from mistakes. What they don’t tell you about the importance of commenting code, is some subtle and psychological little thing. If you are a bad programmer, you’ll never produce good code. But if you are a good programmer, sometimes being in a hurry will make you produce really bad code. There are two reasons why this can happen: 1) you are in a hurry because you’re late with your deadlines. With this, there’s nothing to do. 2) you are in a hurry because you’re just coding fast, on the rush of some ideas that flashed you. In this case, commenting your code a lot will improve drastically the quality of your code. Always write your comments before writing the actual code. This will make you realize it, if your function is not really going to do what it’s supposed to do. Writing the comment will also help you think more about what you’re doing, and being more conscious about it. It will keep your state of mind clear and precise. I strongly recommend using Doxygen to generate a browseable HTML version of your comments, especially if you’re writing a library. Otherwise, it’s still going to keep you on a professional line, which is always a good thing.
Write and use unit tests. If your code is well designed, there are good chances that each function in your code, or each class, performs a specific task in a certain way, and nothing more. Given a certain input, it will reliably return the same output. Right? You have to make sure of that, by writing test cases. Testing the smallest units of your program doesn’t ensure that the whole is working perfectly, but helps. Possibly, append a hook to your Source Code Versioning System (SVN? Darcs?) so that the automatic testing suite will run automatically on the server that hosts your repository, before it accepts your patch. This is quite easy with Darcs.
It never segfaults
Of course this point applies to the languages that allow segmentation fault, or NullPointerException (in Java). It’s easy to get: if your code segfaults, there are no excuses. No matter how stupid the provided input was, your program should not segfault. A good practice, is that each and every function/method would check it’s argument before doing anything. A solid exception handling structure is required. Again, you can object that I’m not really saying anything useful here: “Of course programs shouldn’t segfault, I knew it!”, but think about it: it’s a matter of attitude. You want to write a perfect program, and there are some things you have to keep in mind. Be paranoid with segfaults will implicitly and secretly improve the general quality of your code, without you even noticing.
Writing perfect code is impossible. Especially as the code grows in size and number of programmers. Achieving the impossible, then, is beyond any good intentioned coder. What we can do, though, is just try to have the right attitude, which is about precision, care and, sometimes, paranoia. Writing complex programs is not an easy thing, and, as such, should be handled with extreme care.