[[ Today's guest blog was written by Nick Robertson from the AnsiGL project. AnsiGL is a modern "graphics" API for text-based applications.
The original article, as well as other thoughts by Nick, is over here.
I was particularly struck by point 2, as I've seen so many projects burn up hundreds of hours of time rewriting things that we're already well tested in production. We all have a desire to rewrite - it's part of the programmer mindset - but sometimes it's just a waste of time. Consider reusing libraries (like Boost, as mentioned in the article), or refactoring what you already have, rather than scrapping and rewriting from scratch. --Rich ]]
As I’ve been working on AnsiGL, there have been a few simple “rules” that I’ve been following I go along. Most of these things are probably old concepts to most of you, but hopefully these will help provide a little bit of insight for any up-and-coming programmers and for anyone who might contribute code to the project. I feel it’s important to share some of this not only for better understanding of the code in the project, but also to make sure that any potential developers can strive for the same basic goals. If nothing else, it might help answer why certain things were done the way they were done.
1. Let the compiler do it.
The single biggest rule that I have is one that anyone who has taken a programming course of any kind can tell you: KISS (Keep it simple, stupid). Anytime you can, let the compiler help you generate code. This is the biggest reason for settling with C++ (as opposed to C, for example) as well…for the reduced amount of code that I have to generate thanks to OOP and templates. Reliance on the compiler as an assistant programmer (sort of) essentially allows well-tested code to take the place of something I could otherwise screw up, while still allowing versatility. Not to mention, there’s much less code to test/debug.
2. Previously tested code is better than generating something from scratch.
I’ve also come to really appreciate things the Boost libraries…peer-reviewed code, pre-tested, and I don’t have to type it out myself to boot. Anytime there is another library that meets the requirements (without stepping too far outside the scope of the project…as was the case with most Unicode libraries I reviewed), it should be, at the very least, strongly considered for use in the project. There’s no need to solve problems over and over again unless the needs of the project dictate otherwise.
This “rule” has actually served a dual purpose for me. The above is relatively obvious…the second is that it really makes me think about a particular problem and what exactly needs to be solved. I end up reviewing multiple viable solutions in the process (ie, looking over other libraries that do what I need) and in many cases, I simplify my design. Sometimes the reason is because of interface demands and other times, I was simply thinking about a problem the wrong way.
3. Unit testing is your friend.
It may seem like lots of extra work (and it is), but the payout for the effort is rather large.
For those unfamiliar with the concept…a “unit test” is when a programmer isolates a single “unit” of code, and creates a program with the sole purpose of testing that “unit”s functionality.
AnsiGL is now full of examples of unit tests…even though they were written in haste and are buggy themselves sometimes. I decided to use each class as a “unit” for my purposes, so each class has its own test function. The sole purpose of that function is to test the class’s functionality…usually by manipulating the class by its member functions and verifying the results with expected (hard-coded) values.
This may all seem pointless since these tests don’t really amount to anything in the final product, but when adding new features and debugging, it’s absolutely priceless to be easily able to find what would otherwise possibly be intermittent or hard-to-find bugs. In short, I can essentially run this test application and it will tell me which functions are not performing as expected. This has reduced my overall development time a considerable amount.
Another benefit of creating these tests, is that you get to create your class interface in a somewhat “real” scenario. When designing an interface to your class, it’s rather important to know exactly how you need to use it, how you’d like to use it, and how it must be done…making a unit test forces you to think most of that out well before you make the time investment in lots of code that “doesn’t play well with others”.
4. Less is more.
If you can solve a problem with less code, that is typically the “correct” solution. Anytime I end up solving a complex problem, I almost always think of some way to improve it later on. If that “improvement” ends up being more code for roughly the same functionality, it is almost always the “wrong” answer simply because there is more code to maintain. There needs to be significant improvement (this varies from problem to problem) to justify complex code.
Something I’ve noticed in lots of code out there, is that programmers will program within certain paradigms, just because it’s slightly more efficient for the compiler (for example, ++var instead of var++ in a for loop). In my opinion, there are certain times it does make sense, but most often you should make it simple and easy until the code profiler tells you that it’s a bottleneck. Don’t program around compiler issues (unless it’s simple, like the above example) until it actually becomes a problem, because it can needlessly complicate code! Complicated code is a pain in the ass.