Pillar Guide · Knowledge Hub

Software Engineering: Writing Code Other People Can Change

A guide to the practices that keep software changeable — why readability beats cleverness, what code review is actually for, how to test without a test obsession, and how technical debt really accumulates.

Software Engineering Updated 2026-08-04 1155 words · about 5 min read

Code is read far more often than it is written, and it is changed far more often than it is created. Almost everything worth knowing about software engineering follows from those two facts.

The goal is not elegant code. It is code that the next person — often you, eighteen months later, with no memory of this — can understand and change without breaking something.

The core practices#

Optimise for reading. Clever code that takes ten minutes to understand costs more than obvious code that takes ten seconds, every single time someone opens it. If you are proud of how compact something is, that is usually a warning.

Name things accurately. processData() tells the reader nothing. calculateVatForInvoice() tells them everything. Bad names are the most common cause of misreading code, and renaming is the cheapest improvement available.

Small, focused units. A function doing one thing is testable, reusable and comprehensible. A function you have to scroll through is none of those.

Make dependencies explicit. A function reaching out to global state or hidden configuration cannot be tested or understood in isolation. Pass what it needs.

Fail loudly. Swallowing an error to keep things running produces bugs that surface far from their cause, hours later, with no useful information. Handle it or let it propagate — never silently discard.

Delete code. Unused code is not free: it gets read, maintained, updated and searched. Version control remembers it; you do not need to.

Code review, and what it is for#

Review catches bugs, but that is not its main value — automated tests catch more. The real returns:

Knowledge spread. More than one person understands each part of the system. This is what makes holidays and resignations survivable.

Readability enforcement. The reviewer is the first person to read this code without having written it. If they are confused, so will everyone be.

Design conversation at the point where changing course is still cheap.

For review to work: small changes — a 40-line change gets scrutinised, a 2,000-line change gets "looks good"; fast turnaround, because a review sitting for two days blocks someone; and comments about the code, not the person. Ask questions rather than issue instructions — "what happens if this is null?" invites thought where "add a null check" invites compliance.

Testing without a testing obsession#

The purpose of tests is confidence to change things. Any test not contributing to that is overhead.

Test behaviour, not implementation. A test asserting that a function calls three specific methods breaks whenever you refactor, even though nothing is wrong. A test asserting the correct result survives refactoring — which is precisely when you need it.

Concentrate on logic that matters. Calculations, business rules, edge cases, anything involving money or dates. Not getters, not framework configuration.

Coverage is a diagnostic, not a target. 100% coverage with weak assertions proves nothing; mandating it produces tests written to satisfy the number. Low coverage in a critical module is worth investigating.

Every bug becomes a test. Reproduce it in a failing test, then fix it. This single habit stops the same defect returning and builds a suite focused on where your software actually breaks.

Fast tests get run. A suite taking twenty minutes gets skipped under pressure, which is exactly when it is needed.

Technical debt, accurately#

The term is used for two very different things, and conflating them causes bad decisions.

Deliberate debt is a conscious trade: shipping something simpler now, knowing you will pay later. That is often correct — especially when you are not sure the feature will survive.

Accidental debt is the accumulation of small compromises nobody chose: the workaround that became permanent, the copy-paste nobody consolidated, the abstraction that stopped fitting three requirements ago.

The first is manageable if recorded. The second is what actually slows teams down, and it is invisible until someone estimates a simple change at three weeks.

The practical control is not a quarterly "tech debt sprint" — those get cancelled. It is fixing things in the area you are already working in, continuously, as part of normal work.

Version control that helps#

Small, frequent commits with messages explaining why. The diff already shows what changed; the reason is what a future reader needs and cannot recover.

Short-lived branches. A branch alive for three weeks is a merge conflict and a debugging problem waiting to happen.

Never commit secrets. They persist in history even after deletion. If it happens, rotate the credential — removing the commit is not enough.

Tag releases. "Which version is in production?" should have an answer.

Documentation worth writing#

Most documentation is not worth writing, which is why so little gets maintained. What consistently earns its keep:

  • A README that gets someone running locally in under fifteen minutes
  • Architecture decision records — what was decided, what was rejected, and why. Six months later the rejected options are the valuable part, because they stop someone re-litigating a settled question
  • Runbooks for operational tasks performed rarely and under pressure
  • Comments explaining why, never what. // increment i is noise; // the vendor API rejects batches over 500 is essential

Documentation describing how the code works rots immediately and misleads. The code is the authority for that.

FAQ#

How much testing is enough?#

Enough that you can change the code and find out quickly whether you broke something. If people are afraid to refactor, you have too little. If the suite takes so long people skip it, you may have too much of the wrong kind.

Should we do test-driven development?#

It suits some people and some problems, particularly well-specified logic. It is not a moral position. What matters is that tests exist, are meaningful, and run automatically — whether they came first is secondary.

How do we handle legacy code?#

Do not rewrite it wholesale — rewrites take longer than expected and reintroduce bugs that were fixed years ago. Add tests around the part you must change, change it, repeat. The system improves where you actually work, which is where it matters.

What makes a good commit message?#

A short summary line, then the reason for the change if it is not obvious. Reference the issue. Assume the reader is someone unfamiliar, months from now, trying to work out whether it is safe to revert.

How big should a pull request be?#

Small enough to review properly in one sitting — a few hundred lines at most. Large ones get approved without real scrutiny, which is worse than no review because it creates false assurance.

How do we stop accumulating technical debt?#

You do not stop it, you manage it. Fix things in the area you are already touching, keep changes small, and record deliberate compromises so they are decisions rather than surprises.

What is the single highest-value practice?#

Small changes, reviewed and merged frequently. Almost every other good practice becomes easier when changes are small, and almost every failure mode gets worse when they are large.

What else is coming for Software Engineering

Pillar Guide Ready

The definitive explainer — start here.

Tutorials Soon

Step-by-step, with working examples.

Best Practices Soon

What holds up in production, and what quietly doesn't.

Checklists Soon

Run through before you ship.

Diagrams Soon

The architecture, drawn.

Downloads Soon

Templates and starter files you can edit.

Videos Soon

Walkthroughs.

FAQs Soon

The questions people actually ask.