Rules of the road for developing high-quality software

Rule number one: All code must follow the Porthole Principle. The fundamental issue in programming is that we each look out at the world through a mental "porthole" that limits our field of view. We are limited in how much we can see and understand at any one time. Therefore, systems must be structured so that they can be understood completely, at all levels, even though we are only allowed to investigate them piecemeal, through our own limited and finite cognitive windows.

Abstraction is the essential tool that allows us to create systems of arbitrary size and complexity. A beautifully designed abstraction is easy to understand and use. It is so trustworthy that you don't feel any need to worry about how it is implemented. Finally, and most importantly, it enables you to reason with rigor and precision about the correctness of the code you are writing that makes use of it.

The overall culture of the software group is a commitment to creating clean, bug-free, maintainable code.

Commitment to clear understanding of correctness of every line of code. A programming ethos of a commitment to "getting it right" and the intellectual tools to be able to reason through a chunk of code and convince oneself and others that it is correct.

The code is structured so that the above is possible; the amount of other code that must be understood to understand any particular line of code is bounded and manageable.

Code is structured and documented so that when you have to come back to a given place in the code, it is easy to get your mind into the fabric of assertions, invariants, and assumptions necessary to reason about it correctly and successfully.

Be considerate to the readers of your code (including your future self). The code should be easy to navigate and understand at the forest level, the tree level, and the bark level.

Strive to have the experience of reading your code be a gentle one rather than a harsh one. In the rare cases where a carefully written piece of code remains difficult and might require head-banging on the part of your readers to completely understand it, add a comment so that they don't have to waste their time reverse-engineering your source code.

No stealth inbound dependencies in the code! A stealth dependency is an inbound dependency on a part of the code that is not obvious in the code that is depended on. It is as though adjusting the lighting in the library causes the dishwasher in the kitchen to suddenly stop working. It may be hard to recognize that you are introducing a stealth dependency, because at that moment you have both the library and the kitchen in mind. But other programmers would have no reason to suspect that when they make a change in the library, it also has a stealth effect on the kitchen.

At least two people understand and agree to the correctness of every line of code. (Pair programming, or peer reviews of every line of code.)

Engineers collaboratively discuss and review design approaches and algorithmic approaches early and often during the development process.

The yin and yang of software development are the handling of cases where things go as expected and the handling of cases where errors occur. The overall system design must have support and conventions at all levels for the handling of errors or unexpected situations.

It is essential that developers make their work visible to others on an ongoing basis. Use of configuration management is mandatory, and as a rough guideline developers should check their work in to the shared repository at least once a week, if not more frequently. It is a sin to check in code that breaks the build or breaks regression tests.

It is essential to have software tools that enable the developers to immediately identify the file and line when a fatal error occurs.

Automated regression tests with embedded code coverage metrics. Strive for 100% code coverage of the deployed production system. Do you want a line of code to be exercised for the first time on a running, deployed production system??

Unit tests the developers create while writing and debugging can be conveniently migrated into the automated test repository.

To avoid developer myopia, a non-developer must do a day-long full system test once every week or two.

To avoid organizational myopia, an outside group must do a full system test with training once every two or three months.

Bug database is actively maintained, and test point status in test procedures is tied back to current status of bugs and requested features.

Keep the codebase squeaky clean. No dead code, no sloppy messes in the code. No gratuitously redundant copy-and-paste code. It's not that hard once you get used to it. It is valuable in its own right to make the code understandable and maintainable, and it also fosters an attitude of discipline and care. Sloppy code is probably also buggy code.

Have a standard, consistently applied coding style, possibly enforced using a tool like GNU indent.

Use static analysis tools on the codebase: no compiler warnings; use lint-style static checking tools. Use dynamic analysis tools such as valgrind or Purify.

Use Doxygen or similar to generate API documentation automatically from the codebase. One of the big successes of Unix was the humble man page.

There is a "just right", optimum development speed. Going too slow has the obvious problems of unnecessarily burning schedule and budget. But trying to go too fast also creates problems. The development effort can devolve into a rushed, flustered, panicky state. This results in sloppy, buggy, unreliable, and unmaintainable code that ultimately slows the project down and makes progress increasingly difficult. Developers need enough "breathing room" to do things right along the way, as opposed to rushing off to the next crisis and leaving behind a trail of half-baked, patched messes that will need to be cleaned up later.

If it feels a bit over-engineered and completely bullet-proof to the engineers, it is probably OK. If it feels just basically OK to the engineers, it is likely not really OK.

If the platform isn't stable, nothing else matters.

System security must be designed in, not tacked on.

Perfection is brittle; aim for robustness and trustworthiness.

No one is right 100% percent of the time. Foster a culture of willingness to learn, to be convinced to change your mind, to trust others enough to share new ideas that may seem "off the wall", but may lead to important breakthroughs with some nurturing and reformulation.

Foster ego-free mutual trust: "It doesn't matter who gets the credit".

When making changes, respect the accumulated intellectual capital of your colleagues. Realize that when you change things, you may be costing others time and effort to re-learn something. Make sure your changes are valuable enough to be more than worth that cost.

"Clear and hold:" Whenever a bug is found and fixed, add a test case that differentiates between the failure and the corrected system. Include that test case in the automated regression suite, so that the bug never comes back again, or gets immediately caught if it does.

There are times when we all need help. Create enough trust that people are willing to ask for and receive help when they need it.

Respect the testing staff; make sure they know what to expect at each iteration of the process.

Beginning programmers write simple code. Intermediate programmers write complicated code. Masterful programmers write simple code.