The Engineering Practices I Advocate for on Every Project I Join
by Arif Ikhsanudin, Backend Developer
Some practices are context-dependent — they make sense for certain team sizes, certain stages, certain domains. Others are close to universal. Here's the short list I push for regardless of where I land.
Why I Have a List at All
Contracting across different teams and organizations teaches you something quickly: a lot of the dysfunction is the same. Different products, different stacks, different industries — but the same categories of pain showing up in different shapes.
That repetition has given me a working list of practices that, in my experience, pay back with interest on almost every project. Not dogma — starting points. Things I advocate for early, explain the reasoning behind, and adjust based on what I find.
Some teams already have these in place. When they do, the work is easier and the outcomes are better. When they don't, introducing them tends to reduce friction faster than almost any technical change.
Automated Tests for the Things That Can't Break
I'm not a test coverage zealot. A 90% coverage number on tests that verify implementation details is less useful than 40% coverage on tests that verify the behaviors the business depends on.
The question I ask: what are the things in this system where, if they broke silently, we'd have a real problem? Payment calculations. Authorization logic. Data transformation rules. The core state machine of whatever the product does.
Those things need automated tests, and they need tests that verify the observable outcome, not the internal mechanics. Tests that say "when I give this input, I get this output" survive refactoring. Tests that say "this method was called with this argument" don't.
I'd rather have twenty meaningful tests than two hundred brittle ones. The first set gives you confidence. The second gives you a chore.
Structured Logging With Correlation IDs
The first time you're on-call for a system that produces unstructured logs with no request tracking, and you're trying to trace a specific user's session through three services, you understand immediately why this matters.
Structured logging — JSON, consistent field names, a correlation ID that follows a request from entry to exit — is the difference between a fifteen-minute diagnosis and a three-hour one. On a system under load, it's often the difference between finding the cause and not finding it at all.
Getting this right early costs almost nothing. Adding it to a system that's already in production and logging in fifty different formats is painful and slow.
I push for this on day one of any new service. On existing systems, I push for it incrementally — at minimum, adding a correlation ID to new services and making sure it propagates across calls.
Explicit Error Contracts
Every service has an implicit contract for what it returns when something goes wrong. "Implicit" is the problem. Implicit contracts get violated, because nobody actually documented them, and the callers make assumptions that turn out to be wrong.
When I design or review a service interface, I ask: what does this return on failure, and does the caller have enough information to respond usefully?
A good error response includes:
- A stable, machine-readable error code (not just an HTTP status)
- A human-readable message that's useful for debugging
- Enough context to identify what specifically failed without log-diving
This matters especially in systems where one failure cascades through multiple services. If the root cause error is swallowed somewhere in the middle and replaced with a generic "something went wrong," debugging becomes an excavation.
Dependency Review Before Adoption
Every dependency is a surface of risk you don't control. It might introduce a vulnerability. It might break its API. It might stop being maintained. It might have a license that creates legal complications.
None of these risks are reasons to avoid dependencies — that would be impractical and often counterproductive. They're reasons to evaluate before adopting.
My quick checklist before pulling in a new library:
- When was the last commit? Is the project actively maintained?
- How many open, unresolved security issues are there?
- What would replacing this look like if we had to?
- Does the license work with the project's requirements?
This takes ten minutes. It has saved me from adopting dependencies that looked convenient but came with real problems attached.
Decision Records for Non-Obvious Choices
When the team makes a decision that isn't immediately obvious — why this database over that one, why this architecture over the alternatives that were considered — I push to write it down. Even a paragraph: the context, the alternatives, the reasons for the choice.
The most common pushback: "We all know why we made this decision." The problem surfaces six months later, when two people on the team have different memories of why, and a third person who joined afterward has no idea.
Decision records don't need to be elaborate. A comment in the relevant code, a section in the architecture doc, a ticket in the backlog marked "context." The format matters less than the existence of the record.
Code Review With Explicit Purpose
Code review processes that exist as a gate — "someone has to approve before merge" — are better than nothing. Code review processes that exist with explicit purpose are considerably better.
The purpose I advocate for: catching correctness problems before they ship, propagating knowledge across the team, and maintaining the quality standards the team has agreed on. Not liking my way of doing it.
The distinction matters because it changes what reviewers focus on and how they give feedback. A review process oriented toward correctness produces different comments than one oriented toward personal preferences. And it's considerably less demoralizing for the author.
The practices worth advocating for are the ones that compound — small investments in discipline that pay back in reduced friction across every future change.