Full stack developer
How I write software: tests first and parts you can replace.
TDD, SOLID, continuous integration and a handful of other rules I do not negotiate. Each one with its reason, a case where it applies and the public text that defines it.
- Tests TDD, test pyramid
- Design SOLID, hexagonal, KISS and YAGNI
- Delivery CI/CD, semantic versioning, twelve factors
The rules I do not negotiate
Not fussiness: each one pays for itself the first time something has to change while the program is already running with people inside it.
A small job and a large one are written the same way. The difference is how many times you will come back: software that solves a company process gets touched every few months because the process changes, and what decides whether that is cheap or ruinous was decided on day one, when someone chose where to put each boundary.
- A test before the code If I cannot write the test, I do not yet know what I am asking the program to do. Writing it first is the shortest way to find out.
- One reason to change per part Single responsibility and dependencies pointing inwards: the use case does not know whether PostgreSQL, a file or a third-party API sits behind it.
- Boring wins No dependency gets in without explaining which problem it solves today. What is not needed does not get written.
- Errors are stated, not hidden An API answers a failure in a format the other side can read, with its code and its reason, not with a 200 and a loose string.
- Delivery repeats identically Configuration outside the code, repeatable deployment and a numbered version: the production server is not a handmade artefact.
- Security is reviewed with a list Before calling anything done, the usual pass: authentication, permissions, user input, dependencies and logs.
What I do not do
Most of the work of keeping something cheap to maintain consists of what was left out.
I do not start by choosing a framework. First comes the process to solve and the data to store; the tool comes afterwards, and sometimes the answer is that none is needed. This site is the example: plain PHP, no database, no dependencies and no build step, because nothing it does called for one. It can be read end to end and it is served by a web server and nothing else.
I do not write code “for when we need it”. An abstraction layer with only one implementation is not flexibility: it is one more part to maintain and one more indirection to read. When the second one shows up, it gets extracted, and by then the tests are already written and the change is mechanical.
And I do not leave the interface for last. What you see has its own method, its own contrast and focus rules, and it is not improvised the afternoon before delivery.
Glossary of the craft.
The terms as they are used, with a case where I apply each one and the public text that defines it. So you do not have to take my word: you can check.
- TDD · Test-driven development Write a failing test first, then the smallest code that makes it pass, and only then clean up the result. That is the red-green-refactor cycle, and it settles the design before there is code to defend. Example: In a form validator, the first test is the invalid email: until it fails for the right reason, the validator does not get written. Months later, when a new domain has to be allowed, that test is still guarding the other cases. Source: Test-Driven Development, Martin Fowler
- Test pyramid Many fast unit tests at the bottom, a few integration tests in the middle and very few end-to-end ones at the top. The higher you go, the slower and more brittle they are, so only what genuinely has to be checked whole goes up there. Example: Working out how many holiday days someone has left gets tested dozens of times in unit tests; the whole screen, in a browser, just once. Source: Test Pyramid, Martin Fowler
- SOLID Five design principles: single responsibility, open-closed, Liskov substitution, interface segregation and dependency inversion. They all point the same way: a predictable change should happen in one place, not fourteen. Example: Adding a new payment method should be a new class and a line of configuration. If the order calculation has to be opened up to add “if it is the new gateway, then…”, the design is warning you. Source: SOLID Relevance, Robert C. Martin
- Hexagonal architecture · Ports and adapters Business logic stays in the centre and everything outside —database, API, email, screen— comes in through a port with its adapter. The centre does not know who is talking to it. Example: Sending a form email sits behind an interface: in production it goes out over SMTP and in tests it is written to a file, without touching a single line of the logic. Source: Hexagonal Architecture, Alistair Cockburn
- KISS, YAGNI and DRY · Simplicity first Three shortcuts to the same thing: keep it simple, do not build it until it is needed, and do not repeat the same decision in two places. The order matters, because removing duplication too early also couples things together. Example: This site: no database, no dependencies and no build. The content lives in three files and the site is served by a web server and nothing else. Source: Yagni, Martin Fowler
- Refactoring Changing how code is written without changing what it does, in small steps and with the tests green the whole time. It is not “rewriting it”: rewriting means starting over and losing what was learned. Example: A four-hundred-line function becomes five functions with names of their own, one at a time, running the tests between steps. If something breaks, you know exactly which step did it. Source: Refactoring, Martin Fowler
- CI/CD · Continuous integration and delivery Integrating work into the main branch often, with tests passing on every integration, and always having a version ready to ship. What piles up for weeks breaks all at once, on the same day. Example: Every change runs the test suite before merging; the deployment is always the same script, so shipping is routine rather than an event. Source: Continuous Integration, Martin Fowler
- Semantic versioning · SemVer 2.0.0 Numbering releases as major.minor.patch, where the first number only goes up when compatibility breaks. The number stops being decorative and becomes a promise. Example: Whoever consumes an API knows they can move from 2.3.1 to 2.4.0 without touching their integration, and that a jump to 3.0.0 has to be read first. Source: Semantic Versioning 2.0.0
- Conventional commits Writing the reason for a change with a fixed prefix —fix, feat, refactor…— so the history can be read and the release notes and the next version number come out of it. Example: The project history answers “why is this like this?” without having to ask anyone, which is exactly what a comment in the code cannot do. Source: Conventional Commits 1.0.0
- Twelve factors · Twelve-Factor App Twelve rules so an application can be deployed, scaled and moved without surprises: configuration in the environment, declared dependencies, stateless processes, logs as a stream. Example: Credentials do not live in the repository but in the server configuration file, outside the published directory. The same application starts locally and in production by changing only that file. Source: The Twelve-Factor App
- Problem Details · RFC 9457 A standard format for describing an API error: type, title, status, detail and instance. It lets whoever consumes the API program the bad case instead of guessing it by reading strings. Example: An ERP integration tells “the document does not exist” from “you do not have permission” by the error field, not by whether the message happens to contain the word “permission”. Source: RFC 9457, Problem Details for HTTP APIs (IETF)
- OWASP Top 10 The list of the ten most common security risks in web applications, maintained by the OWASP Foundation. It works as a checklist: most real holes are on it. Example: Before publishing, the usual pass: access control per resource rather than per screen, parameterised queries, dependencies up to date and logs that do not spill personal data. Source: OWASP Top 10
- Product quality · ISO/IEC 25010 The international model that breaks software product quality into nine characteristics —maintainability, security and reliability among them— with their sub-characteristics. It lets you discuss quality with concrete words. Example: When somebody asks for “something robust”, the model forces you to separate which part is reliability, which part is security and which part is simply that the screen should make sense. Source: ISO/IEC 25010:2023
Questions.
Isn’t writing the tests first slower?
On day one, yes. The arithmetic changes as soon as something already in production has to be touched: without tests, every change means checking by hand what used to work, and you pay that every time. With tests you pay it once.
Can you apply SOLID without a framework, in plain PHP?
SOLID is not a library, it is where the boundaries go. You can do it badly with the most modern framework and well with a handful of files: what matters is that the logic does not know where the data came from or which screen it is going to.
What test coverage do you aim for?
I do not chase a percentage. A high number with tests that check nothing gives false comfort. What I want is for the things that hurt when they break —the calculation, the permission, the money— to be covered, and for every production bug to leave a new test behind it.
And if the project already exists with none of this?
You start where you are going to touch. Before changing a part, you write tests that pin down what it does today —even if what it does today is odd—; with that net, the change stops being a gamble. Rewriting the whole thing is almost never the answer.
Who decides when something is finished?
A list written before starting decides, not the feeling that it looks about right: tests pass, the error is reported in its format, the screen works with a keyboard and a screen reader, the version is numbered and the deployment ran the same script as always.
Sources.
Everything claimed here is described in a public text. These are them, with their body and their date.
- ISO/IEC 25010:2023 · Product quality model ISO/IEC JTC 1/SC 7 · 2023-11
- RFC 9110 · HTTP Semantics IETF · 2022-06
- RFC 9457 · Problem Details for HTTP APIs IETF · 2023-07
- OWASP Top 10 OWASP Foundation · 2021
- OWASP Application Security Verification Standard OWASP Foundation
- Semantic Versioning 2.0.0 semver.org
- The Twelve-Factor App Heroku
- Conventional Commits 1.0.0 Conventional Commits
- Continuous Integration Martin Fowler
- Hexagonal Architecture Alistair Cockburn
- DORA · software delivery research DORA (Google Cloud)
Where to go next.
- The stack, tool by tool What each language, database and systems piece does, and how each one gets chosen.
- The applications I have built Where all of this can be seen working, with links to try it.
- And the method on the other side Interface, accessibility and brand system: the rules for what you see.
Shall we talk?
Does this way of working suit you?
Tell me what you have in hand and I will tell you where I would start.

