Writing Solid Code

Bill Sorensen
4 min readNov 14, 2021

--

Someone recommended the book Writing Solid Code by Steve Maguire, saying that it changed their views of programming. I read the second edition (which is difficult to find) and noted guidelines that resonated with me. This article is mainly a list of these, but I have some general comments at the end. (Please note that I’ve extrapolated some of the guidelines and put my own spin on others.)

Guidelines / tips

It’s possible to write bug-free programs.

Use a linter, preferably integrated with your IDE. Turn on compiler warnings.

Use a typed language.

Run the unit tests.

Use assertions to validate preconditions, to check for “impossible” states, to check your assumptions, etc. — but do not use them for error-handling.

Write defensive code — but log/assert/alert if the unexpected conditions occur.

When optimizing or rewriting, consider something like Scientist — have another simple, slow version of the algorithm and compare the results.

Pit of Success — design subsystems that are hard to use incorrectly.

Think about your interfaces from the caller’s perspective. How readable will the call site be? (Writing unit tests or doing TDD helps with this.)

Also think about UX.

Run code coverage to detect logic that’s not covered.

Test error-handling code.

Asking developers to check for error return codes leads to bugs when they forget. Consider exceptions or other techniques that are hard to ignore. Avoid mixing error values (like null or -1) with real data.

Functions should have exactly one well-defined purpose.

Validate arguments and don’t allow invalid ones. Don’t allow “magic number” inputs that do something special (such as 0 making the function do something completely different).

Avoid magic numbers and hardcoded values in general.

Also avoid Boolean arguments; these make the call sites harder to read. The function may also be doing more than one thing.

Test boundary conditions.

Include examples in your documentation, preferably with error-handling.

Make everything as clear and as obvious as possible.

The best functions always succeed; no error handling is necessary.

Consider risk when evaluating a design.

Try to avoid special-case code. (One of my coworkers recently reminded me of the Null Object pattern and we refactored the code to a simpler version.)

Get rid of extraneous conditionals.

Avoid nesting ternary (conditional) operators.

Multiple function return points aren’t necessary bad.

Prefer implementations that are easy to understand (and debug).

Avoid redundancy. (Similar to DRY.)

Avoid premature optimization. This includes the use of bitwise operators to divide by 2, etc.

Avoid risky language idioms. (This reminded me of Crockford’s JavaScript: The Good Parts.)

Beware of operator precedence errors. Mixing operator types can lead to these.

Isolate error handling.

Prefer obvious and boring code to clever tricks. Write straightforward code that newer maintenance programmers will find easy to understand. (I agree 100%.)

Avoid obscure terminology. Be clear.

Don’t rely on internals or on other implementation details.

Avoid global variables.

Avoid “one-line-itis”. (I’ve seen a single-line LINQ expression that ran off the screen…)

Don’t fix bugs later; fix them now. Otherwise the product looks farther along than it is, and it’s tough on morale to spend weeks or months doing nothing but fixing defects. (I can attest to this.) Also, it’s harder to fix bugs in code you wrote months ago. Keep the bug count near zero.

Fix the cause, not the symptom. Track bugs to their source.

Don’t refactor without tests.

Don’t implement non-strategic features, even if they appear to be easy.

Avoid flexible designs and features. Keep things simple and easy to test.

Test all code regardless of origin — whether ported, from Stack Overflow, etc.

Test your code/features as you go. Otherwise it’s too easy to skip testing, especially with a tight schedule. And always test, regardless of the schedule.

Don’t rely on QA to find bugs in your code; that’s your job. And don’t blame QA for finding your bugs.

Focus on writing straightforward code that is easy to maintain and test.

Adopt a “zero bugs” philosophy.

Review

To me this felt like going back in time 20 years. (That’s not surprising, as the first edition was published in 1993.) I remember writing code and stepping through the paths in a debugger around the year 2000, and this is one of the primary techniques the author recommends. It worked; I had a reputation at the company for having very few defects. The problem was if I changed the code I had to tediously step through the branches again. That led me to push back against change requests.

Eventually a conference presenter introduced me to unit testing, and I found that technique gave me similar advantages to stepping through the code. I could also change the code and quickly run the tests to check for regressions. I learned about Extreme Programming (XP) and Test-Driven Development (TDD); now I write tests as a matter of course. The author of Writing Solid Code mentions unit testing in passing, but it’s not a focus of the book.

That said, much of the author’s advice holds up quite well. It reminds me of the excellent Framework Design Guidelines; as the ideas from both books originated at Microsoft, that may not be surprising.

Most of the book’s examples are in C, and a number of the guidelines are specific to that language. If you program in C or C++, you’ll likely find this book even more valuable than I did.

I did not list C-specific guidelines in the previous section, and I also left out ones I didn’t agree with (such as stepping through code in a debugger). If you’re interested in these, I’d recommend reading the book.

For any programmer interested in reducing defects, this book offers valuable advice and a great mindset.

--

--

Bill Sorensen

I have over 25 years of experience developing software, and am 1/2 of Team Sorensen.