Functionality

At the most basic level, all great code does what it was written to do, correctly, efficiently, and robustly. It should be as free of bugs as possible, not waste time doing its job, and handle bad input data in a reasonable way. This is pretty obvious, but there are a lot of software projects in the world that don’t do a very good job of hitting this mark. Too many software projects, after getting to this level, stop. Certainly, there are cases where that’s reasonable — perhaps for a quick bit of one-off code to convert data between two formats, the extra effort of optimizing code or catching all output errors in code might take more time than waiting for the application to complete, or to correct the erroneous output by hand.

For any real code that’s going to be used in a serious way, there are other attributes that can have a major impact on the long-term viability and health of a project.

Utility

It should go without saying that great code is useful, but in practice that’s not always the case. As developers, it’s easy to imagine extra features that aren’t actually in the requirements for a project and then go on to implement them. Sometimes those other features are more fun to write than what we’ve actually been asked to do. Sometimes we imagine what the next thing a client will ask for and have the urge to just implement that as well since we’re already poking around under the hood. Some developers, when confronted with a problem, have the desire to pop up to a higher level of abstraction and work on code that can be used to solve a larger class of problems.

Sometimes the code written under the spell of these urges ends up being needed in the long run. More often, that code just accretes on the sides of your projects like barnacles, unused and unuseful.

Modularity

Great code is capable of standing on its own, or if not on its own, with a small set of truly required dependencies, and requiring as little knowledge about the internal workings of anything else in a program that is possible. Each module should be self-contained enough that you’d be able to drop it into another project and use its functionality without changing the code or dragging along any other unrelated code.

Simplicity

Sometimes it’s tempting to complicate things when writing code, perhaps for the sheer pleasure of an intellectual workout. This urge is a dangerous one — Brian Kernighan probably put it best when he said: “Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”

Sometimes we imagine where code will run slowly and pre-emptively attempt to write convoluted code in an attempt to solve that problem before it even exists; the go-to quote here is from Donald Knuth: “Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.”

We need to remember to write the simplest code that works correctly and then return to optimize those small pieces of code that can be demonstrated to be bottlenecks.

Readability

On the one hand, we as programmers are obviously writing code as our way of telling a computer what we want it to do. In practice, the primary audience for our code is other developers (including ourselves at some point in the future).

In the same way that printers and typographers have developed techniques and conventions to ease the human eye across pages of text, developers need to similarly be aware of how factors like using whitespace intelligently to visually group lines of code that should be logically read as a single unit affect the ability of other developers being able to come to terms with what that code is trying to do, and how it goes about it.

Something we often hear from new developers are grumbles about our standard that lines of code should be limited to 80 characters in width (in the absence of other good reasons to suspend that rule). They’ll object, “…but my new monitor is huge! I can fit 380 characters on it!” forgetting that the readability issue isn’t one of what the hardware is capable of, it’s what the human visual system can be expected to handle. Newspapers and magazines break their text into tall vertical columns for the same reason — reading speed and comprehension are increased by limiting the saccades that need to be made by the eyes when scanning lines of text (see

http://en.wikipedia.org/wiki/Eye_movement_in_language_reading, or “Human Factors and Typography for More Readable Programs” (http://books.google.com/books/about/Human_factors_and_typography_for_more_re.html?id=QstWAAAAMAAJ) by Baecker and Marcus.

Understandability

Great code walks a fine line between the requirement that it encode complex logic, algorithms, and data structures yet remain able to ‘fit’ inside a developers mind so that it can be used and modified without an ongoing need to search through documentation or study of the source. There are simple techniques that can provide great benefit here (some as simple as developing and consistently using sensible conventions for naming things in source code).

Jon Bentley wrote in his book ‘Programming Pearls’ that we should be able to (and want to!) sit down in front of a fire with a glass of brandy to read source code. When developers remember that our real audience is the humans who’ll read our source, we can focus on making our code understandable to them.

Orthogonality

Mathematically speaking, ‘orthogonal’ means that two lines are at right angles to each other — moving along that line on one axis has no effect on your position on the other axis. In software we use this word to indicate code where making changes to one aspect of that code has little or no effect on other parts of the code base. When you’re adding features or maintaining a module, you should only have to touch that module in one place to implement that change. If you’re replacing one module with another, you shouldn’t need to modify other code as a result.

In practice, this can be a difficult principle to follow without careful thought. Developers with experience and the ability to move fluidly between the abstract and the concrete can acquire insights into opportunities to design (and re-design) their systems to enforce or support orthogonality.

Testability

We’ll also occasionally run into developers who believe that their code is so brilliant and subtle that it’s not possible to test it on its own. In practice, when we look at their code, it ends up not being testable because it violates one or more of the characteristics we’ve discussed here — often a piece of code can’t be tested because it’s so tightly integrated with other code elsewhere in the project and doesn’t really exist as a standalone entity. Sometimes it’s designed in a way that makes it particularly difficult to test — the unfortunate side-effect of this is that code that’s difficult to test is probably also going to be difficult to use correctly in a real project.

By having individual developers responsible for testing their code in a repeatable way while they develop, we get dual advantages — fewer bugs will sneak into the finished project, because we now have an opportunity to remove them very close to the time that they were created, and by writing test code to exercise a module the developer is incentivized to make their code easy to work with.

Extensibility

Well-designed code should be extensible as a building block for solving new problems or meeting new requirements without requiring that each time we do this, we need to make modifications to the code. You’ll see this referred to in the literature as the ‘Open-Closed Principle’ (http://en.wikipedia.org/wiki/Open/closed_principle), which says that once a class in an object-oriented system is complete, it should be open to extension (for example by creating subclasses to address the specific needs of new requirements) but closed to modification (other than modifications to correct defects).

The worst case of handling this problem badly is seen on projects where the developers address the issue by copying/pasting working code and then hacking it to fit the new problem/requirements — projects will be littered with numerous almost-identical blocks of code. Working on these projects is frustrating and error-prone unless you’re able to take the time to understand the issues that led to this kind of duct-tape solution and refactor the code into the cleaner design that’s called for.

Maintainability

Code is written once, but maintained for the rest of its existence. Code that’s written to exhibit the other characteristics of greatness listed here is probably going to be easily maintaned as a result. On top of those factors, maintainable code gives fuller information to maintainers in the form of documentation, whether in the form of comments within the source code itself, or as externally maintained documentation.

Comments can be a tricky thing to get right — it’s too easy to provide worthless comments explaining what a piece of code is doing; it’s much more difficult to remember to record one’s thought processes while writing the code to explain why things are written the way they are. Simple or straightforward code is easy to write such that it’s self-documenting, but real projects always include some proportion of code that’s not simple or straightforward. Occasionally we run into developers who refuse to document their code, othen saying that they program so well that all their code is self-documenting. It’s unfair to the developers who’ll come along behind you to ask that they always take the time to puzzle out every chunk of logic that’s not trivial. Worse, it’s a waste of time and money for whoever will be footing the bill for their maintenance work.

James Dueck

James Dueck

James Dueck

Latest posts by James Dueck (see all)

Contact Us