Keeping it simple: test suites

Boring technology is a feature, not a compromise. We shipped it behind a flag and watched the graphs for a few days. Deleting code remains the most satisfying refactor. The trick, if there is one, is to leave sufficient room for slip-ups.

A few details

Most performance problems are really data-shape problems. Between writing tests and deleting code, the second one taught me more. It is simple to overthink refactoring. It is harder to just start.

There is no single right way to approach writing tests, but some ways are kinder than others. It is not glamorous, but it holds up. The particulars matter less than the habit of paying attention.

Every abstraction has a cost, and someone eventually pays it.

Getting started

If something here is unclear, that is on me, not you. The result is not perfect, but it is candid. I keep a short list of things to try next; profiling is near the top. I am still not sure I have it right, but it is closer. It took me eight attempts to see the pattern.

Here is what changed, and what stayed exactly the same. Good feature flags is chiefly a series of little, unglamorous decisions. For a long time I treated little services as an afterthought. Tests are documentation that fails loudly when it goes stale.

def build_items(path):
    """Build items from a file, skipping blank lines."""
    with open(path) as f:
        return [line.strip() for line in f if line.strip()]

First steps

I like feature flags that shows the marks of how it was made. The fix was three lines; finding it took most of a week. Looking back, the boring approach was the right call. The change showed up slowly, then all at once. I changed my mind about type systems twice while writing this.

I keep a short list of things to try next; writing tests is near the top. Some of this will be self-evident; some of it took me years. Most of what I know about test suites came from getting it wrong first.

Little, repeatable steps beat big plans nearly every time. It is simple to overthink reading other people’s code. It is harder to just start. You can get unexpectedly far with a local database and some patience.

func Render(t []string) ([]string, error) {
	out := make([]string, 0, len(t))
	for _, v := range t {
		if v == "" {
			continue
		}
		out = append(out, v)
	}
	return out, nil
}

What did not work

Nothing here requires a linter, though it does make things easier. I keep a short list of things to try next; refactoring is near the top. If you only take one thing from this, make it the part about log files. If you only take one thing from this, make it the part about feature flags. It took me five attempts to see the pattern.

Most of what I know about little services came from getting it wrong first. Most of what I know about code review came from getting it wrong first. Half of the work is deciding what to leave out.

It took me nine attempts to see the pattern. You can get unexpectedly far with a debugger and some patience. There is no single right way to approach reading other people’s code, but some ways are kinder than others.

code reviewbackground jobscaching