Rob Cook


Home | Donate

Heuristics for programmers


2023-04-23

When you've programmed for any length of time, you tend to accrue rules of thumb - heuristics -that guide your work. Here I share a dozen of such rules from my own experience, along with my thoughts on each.

Zero, one, or many

Cardinality issues are often best boiled down to these two simple questions. If we allow three of something why not four, or five? You can be sure that once you ship the code someone will turn around and want one extra than is allowed.

By simplifying things in this way, we can save a lot of design time discussion rather than fret over precisely the right number. We can also more easily accommodate changes in the future by not having specific cardinalities hard coded in to our domain model.

Whilst our domain model may follow this rule of thumb, that's not to say our user interface cannot impose more specific restrictions. Such restrictions imposed in a UI are softer than those imposed in a domain model. Generally speaking a simple UI change should be quicker to turn around than a corresponding model change.

The advice then is to have a permissive data model when it comes to cardinality, coupled to a more restrictive UI.

Fail loudly

Failure of a program should never happen silently. Bring a user's attention to failure in a way suited to your UI. Clearly record in the log file as much useful information about the failure as you can. This is a great aid to investigating defects, and gives you a place to start debugging.

Conversely you should succeed quietly. If things are going as expected there is no need to ring any alarm bells.

Keep interfaces small

Small interfaces are easier to implement. One object in your system can implement many interfaces. Think of interfaces as traits that your objects can possess, rather than a complete description of that object. In this way you provide a more granular way of using objects in your program.

Code to the interface not the implementation

Don't assume knowledge of the underlying implementation. Always write code against an interface. This is a contract that gives you certain guarantees about the actions you are performing. The implementation can be changed or even substituted completely, without breaking the calling code. This is invaluable for growing and maintaining programs.

Form follows function

When it comes to architecture, abstraction should come after the fact. Build something concrete first. Don't design a pretty box before knowing if all the contents will fit. Often you can code yourself into a corner with upfront abstraction. Then you are left trying to fit features into a shape that isn't natural for them.

Do one thing

The Unix Way gave us lots of good philosophical advice for coding. Chief amongst them was the advice to write programs that do one thing (and do it well). This advice can be applied at all levels of coding, from the program as a whole down to an individual line of code.

A program that does one thing has a clearly defined reason to exist. It has purpose. It can be relied upon to perform that one task. It has a clearly defined scope, and its easier to say no to new features. Programs like that tend to have staying power.

Classes and methods (or modules and functions) that do one thing have a clearly defined reason to change. By definition they follow the Single Responsibility Principle. They are easier to understand. Easier to use correctly. Easier to refactor when they change.

Single lines of code that do one thing are simple to read and understand. They are also simpler to debug.

Keep the core free of external dependencies

Think of your program like an onion. Push infrastructure concerns to the outermost layers. Use adapters to map from the outside world to your internal model. Keep external dependencies out of the core.

Perfect is the enemy of good

Make software that is good enough. Sometimes worse is better; less features can be a virtue. Don't chase the diminishing returns of perfection.

Code from the outside in

It is often good to begin coding with input into your program. Get something concrete captured from the outside world. Then work your way inwards to the core of the program. Create interfaces for each step closer to the core you take.

Speak a different language

To progress in your programming ability, you need to stick with a language and its ecosystem for a long length of time. The downside to that is you can get stuck in a rut. Languages are often made of compromises. Unless you visit other languages, you can get stuck thinking your language's particular way of doing things is the only way. That its pain points are universal.

Almost any other language will help in this regard, but try and pick one that has at least one major difference to your current language. Statically typed? Try a dynamic language. Object Oriented? Try a functional language. Class based? Try a prototype based language. You don't want to be too comfortable in your new surroundings.

Syntactic differences are nice to notice, but its the design philosophies that underpin the language and its ecosystem that you want to get a handle on. These are the things that will expand your programming knowledge.

Once you visit somewhere new, bring back the ideas you have discovered and apply them to your main language.

Usage first, implementation after

Use the thing before you define the thing. If you are mid code and discover you need another object, define an interface for it and don't implement it. Refine your usage of the interface, so it fits the situation at hand. Only then should you implement it.

Have fun

Remember to have fun. After all, its just a ride.

end