Search

Enter your search terms

Submit search form

Six language-independent ways to write better code

By Alex on June 25th, 2007 | last modified by Alex on January 1st, 2015

One of the amazing things about working in a department with other coders is that you consistently see the same mistakes being made day after day, month after month, year after year. Whether it’s due to general timidity, fear of criticism being taken personally, lack of a formal review process, or a company culture that does not encourage reflection and improvement, coders are often hesitant to give constructive criticism to other coders. Without such criticism, coders often lack the opportunity to recognize and improve their coding skills, which leads to the same mistakes being repeated in the future. And mistakes and bad habits by one coder on a team ultimately end up costing everyone time, which is always the most constrained resource in a production environment. To minimize such bottlenecks, designing your code the right way the first time is paramount -- and that means having good habits.

The following common sense suggestions have been gleaned from years of experience working both at the professional level and on large-scale personal projects. This article is dedicated to my fellow programmers working at both the professional and hobbyist levels.

1) Plan for extensibility

This is perhaps the #1 issue that I run across on an almost daily basis. Generally when we write a piece of code, we are doing so in response to trying to solve a very specific problem. It is very easy to get stuck in the mindset of “here is the problem I have to solve, and here is how I am going to solve it”. Then a specific solution tailored to that individual problem is implemented, which works great in the short run, but ultimately leads to huge maintenance issues down the road.

Extensibility should always be kept in mind for any piece of code you write. Whether due to feature creep (features not originally anticipated), faster computers, or more sophisticated customers doing more complex things with your program, specific solutions implemented today are going to need to be extended to do something else tomorrow. For example, you may write a small library to handle working with BMP files. Tomorrow your customers are going to want JPEG support too. Today you’re writing certain information to a file, tomorrow there will be 3 times that information that needs to be written to file, and the old files will still need to work too. Your customers are using your software to do a task today, tomorrow they’ll want to do a task with 10 times as many fields, objects, or whatever unit is appropriate to your software. If your code isn’t already set up to support extension, you’ll end up bogged down in maintenance, and every addition will cause your software to become more complex and unmaintainable.

There are many simple suggestions that can help extensibility: Don’t code specific solutions -- code generic, reusable libraries. Don’t assume the length or size of anything will be constant. Version all your user-data files. Use encapsulation.

By far the most time spent in any stage of the software life-cycle will be spent in the maintenance phase -- so it is imperative to design your software in a way that encourages easy maintenance -- and that means designing for extensibility.

2) Don’t use magic numbers

Magic numbers are numbers that are hard-coded into your project, usually without explanation. They often represent explicit assumptions that are being made about the size, length, or value of something. Because magic numbers tend to be somewhat arbitrary, they often do not carry any meaning in and of themselves, which can ultimately make your code harder to read, understand, and maintain.

For example, you might see a piece of code that looks like this:

for iii = 0 to 15
if (slot[iii].type == 12)
return true;

What do the numbers 15 and 12 mean in this context? Why were these specific numbers picked? These numbers do not convey any specific information to the reader about what is going on in this segment of code.

Ideally, magic numbers should be parameterized -- the mechanisms for doing this vary from language to language, but use of symbolic constants (via const variables, #defines, or enumerated values, for example) are much better choices. The above code, rewritten using symbolic constants:

Suddenly, this code makes a lot more sense. Without even knowing much about the rest of the program, it’s pretty obvious that we’re examining the inventory (probably of a creature), looking to see if there are any potions.

Furthermore, using symbolic constants instead of magic numbers aid extensibility. In the future, your character may want 20 inventory slots instead of 15. Using magic numbers, you’d have to search the code for all instances of the number 15, and change them by hand. Changing a symbolic constant usually involves searching for MAX_INVENTORY_SLOTS and changing the definition from 15 to 20. This can not only save a lot of time, but also prevent mistakes caused by inadvertently changing the wrong magic numbers. For example, consider this snippet of code:

if (iii == 15)
return;

What does this 15 mean? Is it the maximum number of inventory slots (in which case you should change it), or does it mean something else (in which case you shouldn’t)? Without understanding the context of the code around it, it’s impossible to tell. That makes doing mass changes when you need to expand the number of inventory slots dangerous, as it introduces the error of inadvertently breaking other code that already works.

(Author’s note: using better variable names than iii is also recommended)

3) Document why, not what

Almost all coders intuitively understand the maintenance value of good comments. And yet, many programmers misuse comments in such a way that they add little to no value to the maintenance process.

Here is an example of a comment that does not add anything:

# Loop through all of the inventory slots
for i = 0 to MAX_INVENTORY_SLOTS

It’s pretty obvious what the code is doing even without the unnecessary comment.

Here’s another useless comment that you sometimes see:

iii = iii + 1; // increment iii

Comments should not be used to describe what the code is doing. The syntax of the code tells what the code is doing. A good comment tells how or why the code is doing what it’s doing. How comments are often useful for outlining the algorithm that the code is using to solve a problem:

/* The following code scans the entire dungeon looking for spots
where a hallway connects to a room. This done by examining
adjacent square of the dungeon. If a square is a hallway
square and the hallway square has one adjacent room
neighbor, this is a candidate square. */
// Algorithm implemented here

How comments are best utilized at the top of a function or block of code, as they allow a programmer to understand whether the following code is relevant to their interests at the time without having to read and comprehend all of the code in the block. This can save a lot of time during the maintenance phase when trying to locate a particular segment of code.

Why comments are ideal as individual line of code comments. Why comments should explain why the coder is doing something specific:

// Because all creatures have been priority sorted by time,
// if this creature (which is the next to move) has a time
// greater than the current time, we can advance the
// CurrentTime variable so that the creature can take it's
// move.
if (pCreature->GetTime() > CurrentTime)
CurrentTime = pCreature->GetTime()

If your code really needs a comment to explain what the code is doing, your code probably needs to be refactored or simplified, not commented.

4) Don’t reinvent the wheel

Programmers in object-oriented languages are often tempted to write their own classes to do things which have already been solved a million times by other coders. This is particularly true with container classes. While writing your own array and linked list container classes can be a good academic exercise, or even fun if you enjoy that kind of thing, why do so when you can reuse one that’s already been written? This not only saves you the time needed to implement the class, it saves you the time of having to test and debug it, and future versions of the class may provide additional functionality without you doing anything other than install them. Furthermore, if the container class is part of the language (such as in C++’s standard template library), programmers reading your code will be more likely to already be familiar with how those containers work, reducing the time needed for new programmers to become familiar with the code.

Reinventing the wheel can also lead to code duplication, which makes your project more complex and more difficult to maintain. If you have two or three pieces of code doing the same task, you may need to modify all of them to enhance their functionality. This is effectively wasted time that could be used elsewhere. Furthermore, it leads to confusion over which version has what responsibility and makes finding errors more difficult (because it’s harder to isolate which code is being used for a particular problem).

One of the complaints heard about some libraries such as the C++ standard template library is that the interface is awkward. If that is the case for you, rather than rewrite a container that already exists, create a wrapper class and wrap the functionality you use most often in your own interface. Generally, the performance penalty for doing so is small, you can tailor the interface to your needs, and if you need to extend the functionality of the underlying library, you already have a convenient place to do so.

There are also plenty of good free libraries out there that may already solve the problems you’re trying to solve: as an example, SDL, FreeType, and WxWidgets are all examples of cross-platform libraries that may offer easy solutions to your problems. Even though you must learn how to use them, often the time needed do so is significantly less than the time needed to implement your own solution, let alone debug and maintain it!

Note: Be careful of licensing issues, especially if using open source code in commercial projects!

5) Work incrementally

One of the biggest mistakes hobbyist programmers make is to try and implement too much at a time without spending adequate time testing each section. As a result, bugs start piling on top of other bugs, not only making them harder to find and isolate, but also making the number of bugs needing be squashed seem unmanageable.

A much better idea is to work incrementally. After each section of code has been written, think, “How am I going to test this?”, and then actually test it. If you find any bugs, either in the code you’ve just written, or in code that your code is relying on, those bugs should be taken care of immediately.

Focus down on one area at a time. Work on one particular piece of code until it does what you want. Every time you switch between areas, your brain has to do a context switch, which makes you less productive and wears you out faster. If you’re writing a container class of some sort, don’t implement only the portion you need now and come back to do the rest later. If you know you’re going to need it later, implement it now, while the details are still fresh in your mind. It is truly amazing how quickly one forgets the details of what one has already coded! You’ll be less likely to make mistakes, and your testing plan will be more coherent and structured.

6) Find someone willing to criticize your work

As alluded to in the introduction of this article, finding someone willing to give you constructive feedback is one of the best ways to learn. If they are willing, this person should be consulted even before you start coding! Bring an outline of your proposed solution to them, explain it to them, and have them give you feedback on it. In the process of explaining it to them, you will not only solidify the problem in your own mind, but they will also be able to offer you advice on things that you may not have foreseen, such as potential problem areas, better algorithms that you had not though of, or where you’re intending to reinvent the wheel.

Once you have coded your solution, have someone take a look at it. They will more than likely be able to point out places where you haven’t made your code extensible enough, where you have used magic numbers, where you haven’t documented well.

Remember, on a team, everyone is in it together. When your teammate writes bad code now, it could be you that ends up having to extend it tomorrow. Consequently, it’s worth everyone’s time to work together to make sure good habits are utilized up front, so the penalties aren’t so severe down the road. If you’re working alone, having good habits will help keep your code structured and flexible, making it easier to work with and less trying on your patience.

Conclusion

Although much of the above seems obvious, these things are often shortcut in situations where there is time-pressure to get things done. However, these suggestions should not be thought of as preventative measures, they should be thought of as investments. Spend a little bit of extra time now, save a lot of extra time down the road. “But what if I don’t have a lot of time now?”, you ask. Do it anyway. Right now is already down the road from some time in the past, and as code ages, the more these kinds of mistakes tend to compound. Doing the right thing now will give you more time in the future to continue doing the right thing. Ultimately, that not only makes for a higher quality product, it makes for a happier programmer.

If you believe this article was worthwhile, please recommend it using the social bookmarking icons below. Many thanks, and happy coding.

Share this:

12 comments to Six language-independent ways to write better code

Program writing is usually referred to as a "problem solving" activity.
To me a program usually "executes a task" (as compared to "solves a problem"...).
Just a thought...
The " how" and "why" distinctions, as compared to "what", are a revelation. Thank you.
Although, some times, its also helpful to hint on the "what" which helps understand syntax in non trivial cases.

Very good post, I wish that all the programmers in the world will one day try to respect those guidelines. The conclusion is really a must.

I only have one comment : the title "Don’t use magic numbers" and the use of the expression "magic numbers", because for me a "magic number" is a specific number (as 0xAB12CD89) used to check a structure of data in memory (for example a hard drive partition structures start) to prevent as much as possible to interpret corrupted data (which often leads to coredump due to overflow, when using length fields in the corrupted data).

As the first sentence says in your paragraph, what you are talking about, for me, is called "hard-coded numbers/values" (in French, as I am, "valeurs en dur dans le code").

Great summary. Most, if not all, of the issues that you bring up are handled by prevalent static analysis tools (disclaimer: such as the one my company sells). Static analysis in this context can be thought of as taking off where the compiler stops, i.e. evaluating the function of the code, not just its syntactical (and surface semantic) correctness. I encourage you and your readers to check out the various tools available in the space -- search wikipedia for static analysis and you'll find links to the most popular ones (of course, I'd love it if you checked out Klocwork first! ;p).

In summary, C++ continues to be a complex language to master, but there are tools out there that can act as the "guy looking over your shoulder" pointing out what you're missing, or when you're applying a potentially damaging semantic or side-effect.

I agree with most of your point 1. Especially "code generically" is a sound advice. The C++ templates offer superb support for generic coding. Even though the syntax is obscure (and often compiler errors too), templates can really help in writing generic, reusable code. They even help *thinking* generically.

Still, if you have a specific, one-off problem, making the solution a reusable library is a waste of time, or maybe even a source for a maintenance nightmare. Writing a good reusable library is difficult, and often it takes a couple of rewrites and revisions, plus more understanding of the problem domain than might be initially available, to make it truly usable.

Now, if your fingers start itching to make a Copy/Paste of the original one-off, it obviously isn't a one-off any more, but a candidate for a reusable library. At that time it might be best to start from scratch and design the library properly, then maybe replace the original code. Not the other way round.

The subnote on item #2 is funny. I'd say iii (or one of the classic alternatives i, j, k and m) is a perfect name for a loop variable. It is sensible to give better names to other variables, of course.

Nice article.
But there is a counter-argument to your first point known as YAGNI: http://c2.com/xp/YouArentGonnaNeedIt.html
In my experience, a balance of the two approaches makes sense. For example, your fifth point somewhat affirms the YAGNI approach.
Also low-effort extensibility can be had by replacing explicit loops with algorithms (STL,etc); b/c container details get encapsulated. Even better, algos relegate magic numbers to once place, the iterator.

Extensibility
CASE statements often recognize the nature of the business process
CASE / EVALUATE PARM-A ALSO PARM-B ... ALSO PARM-Z
WHEN COMBINATION A1-B2-Z9
THEN DO ....
WHEN COMBINATION A2-B2-Z9
THEN DO ....
ELSE DO ....
END

Start with known parms. Use them to find the unknown value of other parms. With each new combination of parm value possibilities, just add a new WHEN... THEN ... Maybe create stubbed out options in advance and have fewer objects to change later.

Re-inventing the Wheel
Oracle, MS, IBM compete for the best DBMS. The DBMS is just a set of re-usable code. There is no way we can write better reusable code than that already written. SQL is just a set of parms sent to the re-usable objects in the DBMS. Re-inventing what is already in the DBMS seems to be the biggest waste of time and money. But it's a great way to create billable hours and full employment on a project.

The drawback of planning for extensibility is trying to solve problems that will never occur. This might be almost as bad as implementing a solution that is too specific. Sometimes the balance is difficult to achieve! Cheers, --daniel

Regarding item #1, I placed DESIGNING FOR EXTENSIBILITY on the same pedestal as PREMATURE OPTIMISATION. A more sane advice is to make your code configurable. And that's all you need. You don't need to define a plugin system.