programming is terriblelessons learned from a life wasted

How I write software

I like to think I write good code, or that I write more good code than bad code.

My favourite property of good code is boredom. Dull statements one after another, no surprises, no tricks, no special cases, and absolutely no meta-programming. Boring code is the easiest to debug, to verify, and to explain.

Boring code usually doesn’t use global state, has very precise effects, and isn’t strongly tied to the project it lives in. Boring features like single assignment, where each variable only holds one object, and not relying on changing the world around it. Boring code will only do what its asked of it and does not rely on implicit behaviours.

Implicit code can be where it is relying on unspecified but implemented behaviour of an API. For example, there is a lot of broken code which relies on the file system returning directory lists in a sorted order, which works most of the time and then fails mysteriously.

Implicit behaviours means there is more to keep in your head at any one time, and code that relies on them become harder to reason about, both locally and globally. When the behaviour changes, it will be a painful process to migrate code from one set of implicit behaviours to another (just ask anyone who has upgraded rails).

Unfortunately explicit code comes at a price, verbosity: boilerplate, repeated chunks of code to invoke a set of functions. If implicit code leads to fragility and spooky action at a distance, explicit code leads to tedious repeated effort.

It is easy to go too far with verbosity, but I will always err towards it — I agree with the Zen of Python that ‘explicit is better than implicit’. Java errs too far, although you can combine all of the bits to read lines from a file, it’s a few lines of repeated code each time. The alternative is convention over configuration, which makes the common use case easy and everything else impossible.

I try to get the best of both worlds by layering my API. The lower half of the api is java-esque: smaller components, simpler behaviours, but requiring effort to use and assemble. The upper half of the api is the humane side: built in workflows with assumptions baked in about how you will use it.

Having two layers of API is something I’ve seen in one of my favourite python libraries, requests. Requests presents a very concise and humane api which covers the majority of use cases, but underneath lies urllib3, which handles the grunt work of HTTP. It’s a lot of work for most internal libraries, but anything significant can benefit from this split between mechanism and policy.

I tend to call the barebones mechanics “modules”, and the policy/workflow parts “libraries”. Along with this split in libraries, I like to split my applications into components and frameworks.

A component is where the business logic in an application or service lives, and the framework is the thin layer of glue that holds these components together. Decomposing an application into components is non-trivial: It’s not just knowing where to break it apart, but knowing if it helps.

To rephrase D. Parnas’ excellent advice: A component exists to hide a hard decision from other components in the system, or to hide a decision likely to change.

Business logic, although finely interwoven, is not necessarily a gordian knot for you to cut into components. If you can’t replace a component easily, it might not be worth splitting out in the first place — it isn’t hiding enough from the rest of the system.

Components should hide business logic from each other, Modules should hide implementation, Libraries should hide workflow, and the framework should hide all the wires holding it all together.

In practice, the boundaries between these parts are not as precise as I’ve described them. Libraries blur into modules, the framework has a tiny bit of business logic in it, and some of the components aren’t good at hiding things.

In the end, I’m just trying to write code that doesn’t require keeping it all in my head when I read or change it. I find layering my code and splitting the business logic into components helps a bit, but really I try to write code that won’t confuse me later on, or worse, terrify me.

I try to write boring code.