At Replo, I was trying to get a handle on how to explain what we should and shouldn’t do. We had a really solid, very extensive, and well-written Notion section of our best practices for code, support process, and RFC, but it was, well, almost too verbose. I was trying to figure out a succinct way to say “here’s a rule that lets us work with fewer rules” or rather “here’s a couple of rules that helps me not have to post about what we do/don’t do on Slack.”
A decade ago at Signal/BrightTag, we had a “code to code” by. I can’t seem to find it on the Wayback Machine, or in any of my old physical notebooks or Notational Velocity archives, but I remember it was good. It explained how to write code without having to check a biblical number of sub-rules and by-laws. Like, working through best practices for how to write a new service should not be like filing a 1040. It had some things like “Network calls can and will fail” and “Test everything” but I can’t quite seem to remember all of them – some of them I probably sublimated (hopefully.)
Unable to remember all of them, I wrote my own.
Here’s what I was able to come up with, and I feel like it’s a good personal guide.
A code to code by:
- A clean workspace is a good workspace.
- We are the operators; no one else.
- Test everything.
- Incremental immediate improvements, long term goals.
- Code reviews are cross-functional.
- Prototype explicitly, productionalize deliberately.
- Build and deploy frequently.
- Edge cases matter.
- Use small, simple building blocks.
- Build things that are easy to reason about.
- Choose only one obvious way to do things.
- Reduce the amount one must remember.
- Focus on code rather than style.
- Fail early.
- Network calls can and will fail.
- Never delete data.
- Together we serve the people.
I think it covers a lot of the bases of writing code with other people, for other people.
A clean workspace is a good workspace.
Programmers naturally want to go fast, get started, build features, and ship code. But you need to go slow to go fast. Having a solid workspace helps you ship good code much faster. Your IDE should be set up properly. You should understand how you’re going to work with code.
This extends to the repo and developer tooling. Manual processes cost you, even if you only do them once a month. Take database migrations, for example. If they require an extremely manual process, they prevent you from quickly resetting your environment to test things. That reset-the-world capability can be a useful part of your flow. Don’t get carried away with setting up your tooling. But still, take off your shoes, hang up your hat and coat, and act like you’re going to be here for a while.
We are the operators; no one else.
We are the ones in charge of what we are building. Not AI, not bots, not code generation tools, not LLMs, and not agents. I would no more say “an LLM wrote that, not me” than “my pen signed those documents, not me.” We are the ones with agency. We are the ones reviewing and approving all code.
Test everything.
Testing ensures code works the first time. It helps us safely make changes so code keeps working as intended over time. Tests are also living, runnable documentation on how the code does and should work.
Incremental immediate improvements, long term goals.
Full-rewrites, large lift-and-shift operations, and V2s are always on the table. But prefer incremental improvements that can be taken right away. They get you closer to a long term goal. They are more likely to work. They are easier to achieve. They help everyone understand the obstacles between where we are and where we want to be. They also make full-rewrites possible, if necessary.
Code reviews are cross-functional.
Code reviews are bidirectional. They help the author write better code. They help the reviewer learn about what’s being committed to the code base. They aid us in internalizing the best parts of each other. They help us frame our work and understand each other. They catch bugs too.
Prototype explicitly, productionalize deliberately.
It’s okay to prototype, hack, vibe-code, or cobble together something. Do it for proof-of-concept, learning exercises, or experimentation. But be explicit about the quality of the work. Be clear about how long that piece of work will last. Prototypes easily slip into production use. This creates unclear expectations about maintenance cost, correctness, or long term value. Prototypes should be called prototypes.
When you turn a prototype into production work, do it deliberately. Completely discard code and assets from the original. Only bring them back with the expectation of professional, high-quality work. Don’t let a prototype slide into production by assuming you can improve the quality later. It may not “have good bones” after all.
Build and deploy frequently.
Building and deploying frequently isn’t about speed exactly. It’s about flow. When working on features, fixing bugs, or maintaining code, you need a tight feedback loop. It makes it easier to experiment and get a sense of what works. A slow build and deploy process interrupts this flow. It makes building software less enjoyable.
Edge cases matter.
Our sense of what is an edge case is often wrong. We should consider edge cases. We should make intentional decisions about what happens in those cases. Then we can measure how frequently they occur. We can understand under what conditions they happen and why. This helps us understand the people using our software better. It helps us understand the environment where our software runs.
Use small, simple building blocks.
It’s easier to reach a novel, complex, and useful solution through small, simple building blocks. Don’t jump straight to the complete version.
This one comes from Figma. It was originally something like “Through small and simple blocks build something new and complex.”
Build things that are easy to reason about.
Complex things are hard. They’re hard to think about, work with, reason through, change, update, grow, or dispose of. The easier things are to reason about, the more people can work with them. They become easier to change. They become easier to delete.
Choose only one obvious way to do things.
The easiest thing to reason about is the obvious thing. You don’t need to reason about it at all—you already know. When writing code and serving people, choose the most obvious way to do something. From obvious, clear choices, we can build novel, useful, and potentially complex solutions if needed.
Reduce the amount one must remember.
Our brains are only so big. They can only hold so much for the long term and in a given moment. Reducing what we need to remember makes it easier to do good work.
Focus on code rather than style.
Style guides, linters, and formatters are useful. But focus on the code—what it does and how it works. Don’t focus on any particular paradigm or style. If it helps, think of style as an emergent property of something purpose-built.
Fail early.
When a task can’t be met, we should know about it as soon as possible. This applies to services, scripts, functions, or other code. Early failure helps us understand the problem and iterate on solving it.
Network calls can and will fail.
All patterns involving IO will inevitably fail. We should account for that at some level. It may be as simple as logging a network call failure. Often it involves more careful design. Applications and services should be network-fault tolerant.
Never delete data.
The obvious exceptions are for compliance or at explicit user request. But in general, don’t delete data. Instead, use simple, reasonable caching mechanisms. Append data to indicate how it’s used. This helps us store it properly for retrieval and use.
Together we serve the people.
We are here with other people, and for other people. Writing code happens alongside others—coworkers, designers, support agents, and more. It’s done for people who will use the software to do something important to them. I prefer “people” over “users,” but I know “users” is a habit. “User” is fine too. But respect everyone in the process. Respect everyone outside the process who may be affected. Together we serve the people.
I think this covers a lot, without being too prescriptive.