Skip to main content

Engineering Principles

These principles are how we translate S&P's core values (Evolution, Integrity, Merit, Care, Teamwork) into engineering decisions. They're not rules to memorise. They're thinking tools. When you're stuck on a technical decision and nothing in this playbook covers your exact situation, come back here.


Why this matters

Every engineering team makes hundreds of small decisions a day. Most of them aren't worth a meeting, an RFC, or escalation. But without shared principles, those small decisions drift in different directions across teams and projects, and over time you end up with a codebase that feels like it was written by strangers.

These principles exist so that when two engineers independently make a judgment call, they tend to arrive at similar answers, not because they were told what to do, but because they reason from the same foundations.


The principles

1. Ownership over territory

Every system, service, and process has a Directly Responsible Individual (DRI). Not a team. Not "everyone." One person.

This doesn't mean the DRI does all the work. It means there's never ambiguity about who makes the call when there's a disagreement, who gets paged when something breaks, and who ensures the thing keeps improving. The DRI can delegate, consult widely, and share the workload, but accountability doesn't diffuse.

This usually works best:

  • Assign a DRI when a system or process is created, not after it breaks.
  • DRIs rotate periodically (every 6-12 months) to spread knowledge and prevent single points of failure.
  • When you're the DRI, you're empowered to make decisions without asking permission, but you're expected to document them (see "Write it down" below).

Critical thinking: DRI doesn't mean gatekeeper. If someone has a better idea for something you own, your job is to evaluate it honestly, not defend what exists. Merit over ego, that's an S&P value. If you're blocking improvements to protect your territory, you've misunderstood the role.


2. Decide at the right level

Not every decision deserves the same process. A useful mental model:

Reversible decisions (one-way door test): If you can easily undo it (a library choice for an internal tool, a refactoring approach, a naming convention in a new module), decide quickly and move on. Bias toward action. You can always change it.

Irreversible or high-cost decisions: If undoing it is expensive or painful (a database technology choice, a public API contract, an architectural pattern that will spread across the codebase), slow down. Write it up. Get input. Use the decision record template (see below).

The nemawashi principle: For decisions that affect multiple people or teams, build consensus before the decision point, not at it. Have the conversations one-on-one or in small groups first. By the time you bring it to a meeting, the outcome should be unsurprising. This isn't politics, it's respect for people's time and expertise. A decision everyone was consulted on gets implemented faster than one that was announced.

This usually works best:

  • One person can make a reversible decision immediately, just document what you chose and why.
  • Irreversible decisions get a written decision record reviewed by at least one other engineer and the relevant DRI.
  • Decisions that cross team boundaries get a lightweight RFC (a written proposal shared async, with a deadline for input, not a meeting).

Critical thinking: The "reversible" vs "irreversible" distinction is a spectrum, not a binary. A library choice for a core service that 15 microservices will depend on is technically reversible but practically expensive. Use judgment. When in doubt, write it up, the writing itself often clarifies your thinking.


3. Measure before you choose, measure after you ship

Opinions are a starting point, not a conclusion. When you're choosing between approaches, find a way to measure which one is actually better: benchmark it, prototype it, look at production data from similar systems. When you ship something, check whether it actually worked.

This applies at every scale: "which ORM should we use" (benchmark both against your actual query patterns), "should we add caching here" (measure the current latency first: maybe it's already fine), "did that performance fix work" (compare before/after metrics, don't assume).

This usually works best:

  • Establish baseline metrics before making a change so you can compare.
  • Use existing data where available rather than running new experiments for every decision.
  • Be specific about what "better" means in your context: faster? More maintainable? Fewer incidents? Cheaper? These often trade off against each other.

Critical thinking: Data-driven doesn't mean data-paralysed. Sometimes the data isn't available, the cost of gathering it exceeds the cost of being wrong, or the decision is genuinely a judgment call. In those cases, make your best call, document your reasoning, and define what signal would tell you later that you got it wrong. The goal is to avoid the pattern of "we chose X because someone had a strong opinion in a meeting."


4. Prefer the boring choice for important things

When a system is critical (handles money, holds customer data, sits in the hot path of every request) the right technology choice is usually the one your team already knows well, that has been in production at scale for years, and that has boring, predictable failure modes.

Save the exciting, cutting-edge tools for internal tooling, experiments, and non-critical systems where the blast radius of a surprise is small.

This usually works best:

  • For databases, queues, and core infrastructure: use what the team has deep production experience with.
  • For new libraries or frameworks: try them on an internal tool or a low-risk feature first. If they work well, they earn their way into critical paths.
  • When evaluating new technology, ask: "what happens when this breaks at 3am? Do we have someone who can debug it?"

Critical thinking: "Boring" doesn't mean "outdated." It means proven, well-understood, and debuggable by your team. Sometimes the new tool genuinely is better: faster, simpler, cheaper. The point isn't to reject novelty; it's to make sure you're choosing it for the right reasons (it solves a real problem better) rather than the wrong ones (it's trending, it looks good on a CV, the conference talk was exciting).


5. Make it easy to do the right thing

If the right engineering practice is harder than the shortcut, people will take the shortcut, not because they're lazy, but because they're under pressure and the system incentivises speed over quality. Your job as an engineer is to make the right path the easy path.

This means: if tests are slow, fix the test infrastructure before blaming people for skipping tests. If the deployment process has 14 manual steps, automate it before writing a runbook that says "please follow all 14 steps." If code review takes three days, fix the bottleneck before writing a policy that says "review within 24 hours."

This usually works best:

  • Automate checks that humans forget: linting, formatting, type checking, security scanning, all in CI, all blocking.
  • Provide templates and generators for common tasks (new service scaffold, new API endpoint, new component) so the "right" structure is the default.
  • When you find yourself writing a process document that says "remember to...", ask whether you could make it automatic instead.

Critical thinking: There's a balance between automation and over-engineering. A process that runs three times a year probably doesn't need a custom CLI tool. A manual step that someone does five times a day definitely does. Invest automation effort where the frequency × friction is highest.


6. Write it down

If a decision, a process, or an understanding exists only in someone's head, it doesn't exist. It's unavailable to the person who joins next month, to the you who'll debug this at 2am in six months, and to the colleague on a different project who's solving the same problem right now.

Writing things down isn't bureaucracy, it's a multiplier. A 15-minute write-up of "why we chose PostgreSQL over MongoDB for this project" saves hours of re-debate when someone asks the same question six months later.

This usually works best:

  • Decision records live in the repo next to the code they affect (see Architecture section for the ADR template).
  • Runbooks and process docs live in the playbook (this repo) or in the project's own docs folder.
  • Meeting notes that contain decisions get captured as action items in Jira and as doc updates where relevant, not just in a Slack thread that nobody will search.
  • Write for the reader who has no context: future you, the new hire, the person on another project.

Critical thinking: Not everything needs to be written down. A quick Slack discussion about where to put a utility function doesn't need a decision record. The line is roughly: if the decision took more than 10 minutes of discussion, or if someone might reasonably ask "why did we do it this way?" later, write it down.


7. Small steps, shipped frequently

Large changes are hard to review, hard to test, hard to debug, and hard to revert. Small changes (a single concern per PR, deployed frequently) are easier on everyone. They reduce risk, speed up feedback loops, and keep the codebase in a continuously shippable state.

This isn't just about Git commits. It applies to how you think about building features: break the work into slices that each deliver a small, complete piece of value rather than building all the layers of a big feature and shipping them all at once.

This usually works best:

  • A PR should be reviewable in under 30 minutes. If it takes longer, it's probably too big.
  • Ship behind feature flags when a feature isn't ready for users but the code is ready for production.
  • Deploy to production frequently (ideally at end of each sprint for active projects). The longer you wait between deploys, the scarier each deploy becomes.

Critical thinking: "Small" doesn't mean "incomplete." Each PR should leave the codebase in a working state. Splitting a feature into 10 PRs that each break something until the last one lands isn't small steps, it's a mess in 10 installments. The art is finding slices that are both small and self-contained.


8. Leave it better than you found it

When you touch a file to fix a bug or add a feature, and you notice something nearby that could be improved (a misleading variable name, a missing test, a duplicated function) fix it. Not everything. Not a full rewrite. Just the small, obvious improvement that's right in front of you.

Over time, this compounds. The codebase gets cleaner with every commit rather than gradually rotting. It also normalises quality improvement as an everyday activity rather than a special "tech debt sprint" that never gets prioritised.

This usually works best:

  • Small cleanups go in the same PR as your feature if they're in the same file and don't muddy the diff.
  • Larger cleanups (affecting multiple files, changing interfaces) get their own PR and their own Jira ticket.
  • If you notice something that needs fixing but it's outside the scope of what you're working on, create a ticket for it. Don't just sigh and move on.

Critical thinking: Use judgment on timing. If you're in the middle of a critical hotfix at 11pm, now is not the time to rename variables. The "leave it better" principle applies to normal development flow, not emergencies.


Decision record template

When a decision warrants documentation (see Principle 2), use this format. It mirrors the S&P DACI board on Confluence so that decisions flow seamlessly between the repo and Confluence.

Store decision records as Markdown files in the relevant repository under docs/decisions/.

# DR-XXXX: [Short title]

| Field | Value |
|------------------|----------------------------------------------------|
| **Status** | Not started / In progress / Complete |
| **Impact** | High / Medium / Low |
| **Driver** | [Name, the person driving the decision] |
| **Approver** | [Name, the person with final sign-off] |
| **Contributors** | [Names: people contributing input and expertise] |
| **Informed** | [Names: stakeholders who need to know the outcome]|
| **Due date** | YYYY-MM-DD |
| **Resources** | [Links to relevant research, pages, related DRs] |

## Relevant data

Add any data, benchmarks, or feedback the team should consider when making
this decision.

## Background

What situation or problem prompted this decision? What constraints and
challenges exist?

## Options considered

| | **Option 1: [Name]** | **Option 2: [Name]** |
|--------------------|----------------------|----------------------|
| **Description** | | |
| **Pros** | | |
| **Cons** | | |
| **Estimated cost** | Large / Medium / Low | Large / Medium / Low |

## Action items

- [ ] [Action to close the loop on open questions or concerns: @owner, due date]

## Outcome

What did we decide and why? What changes as a result?
What signal would tell us this was the wrong call?

When to use which format: For decisions that live in code repos (architectural choices, library selections, API design), store them as Markdown DRs in docs/decisions/. For decisions that involve client-facing work or cross-functional stakeholders (cloud provider comparisons, infrastructure proposals), use the DACI board template directly in Confluence, see the Cloud Providers Comparison template for a real-world example. The fields are identical so a decision can move between formats without losing information.


Checklist

Before making an engineering decision, run through these questions:

  • Is there a DRI for the system or area this affects? If not, assign one now.
  • Is this decision reversible or irreversible? Am I giving it the right amount of process?
  • Have I consulted the people this will affect (nemawashi), or am I announcing a surprise?
  • Am I choosing based on data, or am I rationalising a preference?
  • Does this choice make the right thing easier or harder for the next person?
  • Will this be understandable to someone with no context six months from now?
  • Is there a written record if someone asks "why did we do it this way?"

AI tips

  • Decision records: Use AI to draft the initial structure of a decision record: describe the context and options verbally, and let AI format it into the DR template. Review critically: AI tends to make every option sound equally reasonable. Your job is to add the real-world constraints and preferences that tip the balance.
  • First-principles thinking: When you're stuck on a technical decision, describe the problem and your constraints to AI and ask it to reason from first principles rather than recommend a specific tool. This often surfaces considerations you hadn't thought of.
  • Challenge your assumptions: Ask AI to argue against your preferred option. If it can't make a compelling case, your choice is probably solid. If it can, you've found something worth investigating.

Resources