Thoughts on Programming: September 2024

This document summarizes the most salient aspects of my programming style and philosophy.

Goals

  1. Justified confidence that the code is doing what you meant it to do, and can be deployed to production without causing problems.
  2. Smooth, swift development, enabled by understandable code that is easy to gain confidence in (through verification).

Testing

Testable design

Test feedback

Test coverage

TDD

If test coverage is useless, how do you know if code is really tested?

This is a nice philosophical argument for TDD, but I think the real value of TDD is:

Shallow Hierarchies

DIY

Functional Programming

Data

Persistent data stores are clients

Treat data coming from your database the same way you treat HTTP requests from old versions of your mobile app.

That means that when you change your data model, you must either:

  1. Upgrade all old data to have the latest format at rest (analogous to forcing all clients to upgrade to the latest app version)
  2. Maintain compatibility with old formats

It also means you shouldn't trust raw persisted data to have the "right" format. Always parse data into a typesafe form, and take care not to delete or corrupt data that your code can't handle.

OOP

Uses for Objects

While I prefer to use pure functions and immutable data in most of my code, there are times when a mutable object comes in handy.

"Faux-O" objects that you can treat as immutable

Rules for Objects

Organization

We can partition the code for an application into various "bins" —

The overall dependency graph of an application codebase should look approximately like this:

3 rows of boxes in a pyramid shape. The topmost box is labeled "Application". The two boxes in the middle row are labeled "Parsing and serialization" and "Business domains". The three boxes in the bottom row are labeled "Platform", "Language-level utilities", and "Technical domains". Arrows point from "Application" to each of the other boxes, from "Parsing and serialization" to "Business domains", and from every box to "Language-level utilities"
The pyramid architecture? Is that name taken?

We'll go through each part in turn, bottom up.

Language-level utilities

This bin is for general-purpose functions that manipulate the language's built-in datatypes (arrays, strings, structs). Underscore, Lodash, or Ramda are libaries that fit this purpose. In a maximalist language like Ruby, this bin might not be necessary because everything you need is built into the standard library.

Technical domains

This bin is for code which:

Examples of technical domains: HTTP (just the protocol, independent of transport), HTML, Markdown, URLs, JWTs, cryptography

Platform

This bin is for code that depends on details of the platform where the program is running, but doesn't care what the application is for.

Examples:

Business Domains

This is the heart of the program. It knows what the program is for, but not what platform it's running on, or how users will interact with it.

Examples of business domain concepts:

Parsing and serialization

Parsing code knows how to take data that has been read from the database or some other external source (via Platform code) and convert it into the format used by the Business Domain code.

Serialization code does the opposite, converting from Business Domain format to the format that will be written as output.

Parsing and serialization code depends on Business Domain code because it knows how to create and interpret values of Business Domain types.

Application

Application code contains the user interface, and the glue code that holds everything else together.

Consequences of this organization scheme

If the app needs to be replatformed, the Business Domains, Technical Domains, and Language-level Utilities can all stay as they are, because they are pure functions and have no dependencies on the other parts. The other parts may need to be rewritten to various degrees.

Platform, Technical Domains, and Language-level Utilities are candidates for extraction into libraries or reuse in new applications.