Sometimes people aren’t sure what to look for when reviewing code. This page is to give a baseline of the most important things we think you should look for.

Following these principles will help us keep a high quality code standard:

Readability

  • Code should be self-explanatory. If you need to write a comment explaining what the code does, refactor it instead.
  • Comments should explain why something was done, not what it does.
  • Always consider how the next person modifying or understanding the code can do so faster.
  • Use clear and descriptive names for functions, variables, and structures.

Complexity and Simplicity

Embrace KISS (Keep It Simple Stupid) – Try to think more like grug brain developer.

One of the ways to be grug brained, is reduce the amount of state in our code.

  • Minimize amount of code:
    • For every 1,000 lines of code, there’s a bug. Write only what is essential.
    • Always ask yourself: “How can I simplify or remove this code?”.
      • Do it on code you’ve just written, code you’ve written a year ago, or someone elses.
  • Functions should use the minimum number of variables necessary.
  • Follow the Single Responsibility Principle (SRP):
    • Each function should accomplish only one task.
    • Don’t name a function with “and”, this most likely means you should separate the functions.
      • For example: createTableAndWriteToDisk() should most likely be writeToDisk(createTable()).
  • Minimize the number of fields in a struct.
    • Do you have 2 fields that both hold the same state? Example: Pointer to variable + boolean of whether the variable is nil.
  • Treat the database as the most critical state. Bad in-memory state can be fixed by restarting the process / pod, database state is different. For example:
    • Avoid splitting related database operations across multiple queries; use transactions.
    • Research proper rollback mechanisms (e.g., GORM transactions with panic handling).
  • Don’t remember to do things, add CI / CD guardrails (e.g. linter rules).
    • The more stuff you need to keep in your mental state, the more stressful you’ll be, the more time it will take you to context switch between tasks, and more bad stuff will reach the main branch unnoticed.

Error Handling

  • Anticipate potential failures in every line of code.
    • Consider crashes (e.g., node failure, pod restarts, or panics).
    • Ask: “What happens if a panic occurs here?”
  • Always handle errors (do at least one of):
    • Log them.
    • Propagate them up the call stack.
    • Apply corrective actions where applicable (e.g., undoing database changes).
    • Last resort: Comment why you ignore the error!

Refactorability

  • Modularize repetitive logic — if you do something more than twice, make it reusable (e.g., via an interface).
  • Design for extensibility:
    • New features should involve adding code, not rewriting or removing existing code.
  • Think functional - Keep state modifying / side-effect code contained in small modules (functions / structs). It’s a whole lot easier to read, learn, test, and modify code that is purely functional.

Architecture

Strive to write stateless, boring, glue code.

Our microservices are the perfect environment for that. The microservices guide is the best resource to understand our philosophy regarding distributed systems design.

If you need something more complicated than a stateless microservice (e.g. a database, stateful data pipeline), reach out to some external (preferably open-source) project, and don’t implement it yourself (e.g. Postgres, Spark).

Keep everything operationally simple, our DevOps team will thank you:

  • Always try to design with the existing infra, before adding new infra. Examples:
    • Re-use Postgres for pub/sub. Example: Pub/Sub.
    • Re-use Redis for queues (Redis Streams).
    • Re-use Postgres JSONB for semi-structured data (instead of MongoDB). Example: The query params for our automations & endpoints service.
    • Re-use Postgres for vector data via pgvector (instead of Quadrant). Example: Our embedder service.
    • Re-use Temporal as fault-tolerant data pipelines (instead of Spark). Example: Our datadiscoverer service.
  • Read more @ Radical simplicity, Postgres for everything.
    • Go through and read the description of 200+ postgres extensions @ trunk (postgres package manager), just to get a feeling for how much you can achieve with this elephant.

Only if one of our existing technologies doesn’t suffice (and be intellectually honest and purely technical with the conclusion), then can you integrate the new infra required to operate this new project.

Testing

Testing is part of the product, never cut corners on tests because “a user doesn’t see our tests”.

They do, when something breaks our reputation is hurt a lot more than not releasing some feature on time. We’ve already seen this with existing customers / POVs.

Feel like tests are a chore? Think of creative ways to improve the tests “product”, what abstractions are missing? Take the time to implement these improvements.

Types of tests:

  • Unit tests - In the age of LLMs, it shouldn’t take too much time generating boiler plate of tests with some test cases, just don’t forget to add edge cases, this is exactly where we can fall, and the AI won’t think of good edge cases, only common cases. Unit tests are amazing as they run really quick, and very easy to run, allowing for a quick feedback loop.
  • Integration tests - As our microservices are mostly glue code, connecting different systems together, integration tests are the 80/20 of amount of effort / coverage.
  • E2E - Mostly sanity tests, these are harder to write without them being flaky, as they interact with the real world and 3rd party services. To keep flakiness low, there should be only a few E2E tests per feature that cover as much as possible in a single test.

If a test is flaky, skip it immediately, and open a ticket so it will be prioritized and fixed. We don’t want to waste other people’s time.

Performance

Performance is less critical than other aspects but can easily degrade if overlooked, as often happens when you run fast.

Remember time complexity only matters when you reach large scales of data.

As database queries are usually the bottleneck in microservices, remember to optimize database queries:

  • Add indexes to frequently queried columns (e.g., those in WHERE clauses).
  • Reduce time complexity where feasible (e.g., replacing nested loops with single-pass algorithms).

General rule of thumb: first optimize everything you can out of 1 thread before going multi-threaded, and then distributed. In my experience, most code can be optimized at least 10x before needing to be distributed.