Skip to main content

TIGER_STYLE.md used by tigerbeetle

https://github.com/tigerbeetle/tigerbeetle/blob/0.17.4/docs/TIGER_STYLE.md

The architecture and code style guidelines for the tigerbeetle project are encapsulated in what the team behind tigerbeetle calls TigerStyle. Since tigerbeetle is a financial transaction database, they want to hold their library to NASA-level standards, which is both important and admirable.

I think there are a lot of good ideas in their style but their style works for the tiny team they have. I don't think TigerStyle is easy to port to other teams, but some of the concepts are.

Simplicity and Elegance

When we write modules, ranging from single classes to things like libraries, we seek to clearly describe an operation and its components. When a module is simple, it's easy to talk about it at a high level.

I don't really like this section as a whole because it's very vague, but I think some great takeaways are:

  • The notion of "simplicity" should not be used as a cudgel in discussion
  • Measure twice, cut once, measure again: simplicity is something to continually achieve through iterative planning as your idea may need to change as it is implemented

Technical Debt

I also don't really like this section as a whole either because it assumes a perfect world of infinite time and budget. I do like the stance of "if we encounter a showstopper, we take the time and solve it versus merely bandaging it". A good team split or even AI usage opportunity here might be assigning someone to patch while the group begins planning and iterating on a proper and complete resolution.

This section is also reliant on the Simplicity and Elegance section, where the tigerbeetle maintainer assumes ample time spent in pre-code implementation design.

The maintainer is also willing to be lacking in features while meeting initial design goals instead, believing that meeting design goals allows for stable progression (i.e., you're not going back and constantly patching a system you thought was a done deal).

Safety

They do literally use NASA's guide for safety critical code, which isn't really 1:1 portable to less critical systems (internal webapps, limited-operation desktop apps, etc.).

Some of the portable pieces here are honestly just good general design that other books and blog posts mention:

  • Declare variables at the smallest possible scope. Example: for something like a Java class, try to avoid littering the class with private fields that are used by singular methods within the class, and instead declare a variable within the method instead.
  • Keep methods short. The tigerbeetle team enforces an arbitrary 70 line limit per method as a way to enforce constraints internally. I think this is a good yardstick in general: if your method is getting close to 70 lines including spacing, it's a sign that you may want to start federating logic from that method into smaller (private) methods.
    • "When splitting a large function, try to keep all switch/if statements in the "parent" function, and move non-branchy logic fragments to helper functions." - I personally started doing this before reading this document in full due to a lot of my personal project work having quirky states, such as string "cleaning" due to things like video game input notation. I really like this one and one of the devs came up with a one line summary for this style that I'll be using as well: "push ifs up and fors down (dev's blog post)"
    • "Let the parent function keep all relevant state in local variables, and use helpers to compute what needs to change, rather than applying the change directly." - also another one I started doing after learning the hard way on another personal project
  • Have a limit on everything. Example, a loop or something that calls a loop should have a finite upper bound to prevent an infinite loop.
  • Compiler warnings should probably be treated as errors; you can choose to ignore them, but they will pile up and pollute the logs of things like build scripts, and/or they may be hiding failure states you aren't aware of.
  • Big fan of "State invariants positively." If some condition has to be true for your method to work, you should write your assertion (usually an if statement) of that condition such that it should represent the "happy path" of your method.
  • Also a big fan of "Always motivate, always say why." Use comments to explain the rationale for decisions in methods and even the reason for a method's existence.

Performance

Having the mindset of "always consider performance" as a developer, even when working on things like CLI tools, will make you a better developer over time.

If you're doing low-risk development, like webapps, you can lean a little bit into the notion of "RAM capacity is massive" but remember that your program will be competing with other programs for resources. Be the better developer. Read this section even if you aren't doing things like millisecond financial transactions; some of it may not be applicable to your project, but the mindset makes your app better.

Developer Experience

This is kind of a difficult section because a lot of it is tightly coupled to financial transaction safety and the Zig programming language.

Some points I highlight:

Infuse names with meaning

Example: placeholder names like "list" and "arr" are useful when in the midst of writing for storing collections of items, but the purpose and content of the collections should be evoked from the variable name. Difficult to provide examples that haven't faded with time since most of my committed work is private (aka it makes money for an employer/client), but this is an example from years ago when I was fooling around with writing a Discord bot.

Get the nouns and verbs just right

This is definitely a callback to the "Simplicity and Elegance" section, where you want to clearly describe an operation and its components such that you could explain it to almost anyone.

One of the big picture items that keeps coming up is "you will have to talk to other people, technical and non-technical, about the codebase". When you write a method name, it should be something that accurately describes what's happening in the method and it should make sense in the context of your system (class, collection of classes, library, etc.).

Dependencies

I think this is a weird one. The tigerbeetle library probably benefits from NIH syndrome because they need a very high degree of control and precision.

Webapps tend to suffer from supply chain attacks the most since they're attractive targets with easy entry points (npm for JavaScript/TypeScript, pip for Python, etc.), but libraries such as lodash provide a lot of functionality that developers expect from languages like C#. Sometimes it's better to suck it up and roll your own with the JS/TS standard library instead if you only need that functionality in one module: why have an extra dependency you barely need?

I think most people not working on high precision systems like the tigerbeetle library can adopt a slightly looser approach: aim to keep external library usage low. If you want to introduce a library, make sure it's going to be important to the codebase as a whole.

Tooling

I actually agree with this one pretty strongly and it's why I'm a big proponent of C#'s new file-based app format. I personally don't mind learning multiple programming languages, and I do like having something like Python in my back pocket just in case, but being able to write supporting tooling in the same language as the app you're developing keeps your team agile: if you can run the app, you can run the scripts, and no runtime/operating system/etc. shenanigans are gonna get in your way.