I’ve been writing software since 1982.
That means I’ve had the privilege of watching the industry repeatedly discover the same problems, rename them, wrap them in a framework, sell a certification course, then act surprised when reality still refuses to follow the plan.
AI is just the latest excuse to do it again.
Apparently software development has changed completely because machines can now write code.
They can.
Very quickly.
Sometimes frighteningly well.
But here is the awkward bit.
Writing the code was never the hardest part.
The hard part was, and still is, figuring out what the hell we should build when we don’t fully understand the problem yet.
We start projects with incomplete knowledge.
Users don’t know everything they want.
Developers don’t know everything the system will encounter.
Architects don’t know everything production will teach them.
Requirements change because our understanding changes.
This is not a failure of planning.
It is the normal condition of software development.
Yet we still build processes that behave as though somewhere, if only we held enough workshops, wrote enough stories and filled enough Jira fields, certainty would eventually emerge.
It doesn’t.
It just becomes better formatted.
Then comes the next trick.
Once the documents start drifting away from reality, we decide the answer is more documentation.
More ADRs.
More tickets.
More acceptance criteria.
More architecture diagrams.
More project wikis.
Soon the useful knowledge is buried under twelve months of perfectly respectable paperwork that nobody quite trusts.
And because nobody quite trusts it, they ask Dave.
Dave left eight months ago.
Excellent.
So now AI arrives, and rather than questioning any of this, our first instinct seems to be to teach the machine to consume the entire pile faster.
That should end well.
Then there’s testing.
I work in that world now, so I probably shouldn’t say this too loudly, but here goes.
We have spent years convincing ourselves that we can test our way out of an understanding problem.
More tests.
Faster tests.
More automation.
More coverage.
Shift left.
Shift right.
Shift somewhere else.
Now AI can generate the tests too, which is marvellous.
Except there’s a small problem.
A test can tell you whether the system behaves according to an expectation. It cannot tell you whether the expectation was right.
If the requirement is wrong, and the code is generated from that requirement, and the tests are generated from that same requirement, congratulations.
You now have the wrong thing with excellent test coverage.
A green pipeline is not the same thing as understanding.
It may merely mean we have become extremely efficient at agreeing with ourselves.
That distinction has become central to something I’ve been experimenting with.
I’ve been calling it WoW, mostly because every collection of files eventually needs a name and apparently “some YAML and a grumpy development philosophy” wasn’t going to fit.
The idea is simple enough.
Stop pretending software development is a production line.
Treat it as a learning system.
At any point in time, a project should be able to distinguish:
- what we know;
- what we believe;
- what we don’t know;
- what we have consciously decided;
- what we tried;
- what reality taught us;
- what must not be broken accidentally.
That last one has become increasingly important.
Or, in the less formal version:
Don’t break working shit unless breaking it is the experiment.
That doesn’t mean preserving every bit of legacy behaviour forever.
Some legacy behaviour deserves to die.
Preferably before it develops a fan club.
It means distinguishing between behaviour we are deliberately changing and behaviour we already have evidence should survive.
If we are changing one thing, we should not casually destroy three others while congratulating ourselves on moving quickly.
This is where test-first development still matters enormously.
When we understand the desired behaviour, write the evidence first.
Red.
Green.
Refactor.
Still works.
No revolution required.
But brownfield systems are different.
Sometimes we genuinely do not understand the behaviour.
Then pretending we can write the “correct” test first is nonsense.
Observe it.
Characterise it.
Change something small.
Learn.
Then decide what behaviour actually deserves to survive.
The test is not the truth.
It is evidence.
And there is another distinction I think we’ve blurred for years.
Verification is not discovery.
Verification asks:
Does the system behave according to something we already believe should be true?
Discovery asks:
What does reality tell us that might change what we believe?
Unit tests are generally excellent verification.
Contract tests are excellent verification.
Regression suites are excellent verification.
Production telemetry can be discovery.
User behaviour can be discovery.
Load behaviour can be discovery.
A failed experiment can be discovery.
Deleting a week’s implementation after learning something important can be progress.
Try explaining that last one in a status meeting organised around percentage complete.
This is also where AI gets interesting.
AI makes implementation cheap.
Very cheap.
That is potentially transformative.
But cheap implementation does not mean cheap mistakes.
In fact, AI can spread a bad assumption through thirty files before a human has finished explaining why the assumption was wrong.
So the development question becomes less:
How quickly can we generate code?
and more:
What is the cheapest safe action that will reduce an important uncertainty?
That might produce code.
It might produce a test.
It might produce telemetry.
It might produce a tiny production experiment.
It might result in deleting something.
The unit of progress should be validated learning, not keystrokes, tickets or generated code.
And when we learn something, that learning should survive.
Even if the implementation doesn’t.
That leads to another problem I’ve been trying to solve.
Project memory should evolve with the project.
Not as a giant journal that every developer or agent has to read from the beginning.
That would be insane.
Instead, I want three temperatures of memory.
Current understanding.
Local context for the task at hand.
Deep history when somebody needs to know why we believe something.
History should not be something every actor is forced to read.
It should be something the project can explain when asked.
And now “actor” matters.
Because my next team member might be human.
Or Codex.
Or Claude.
Or Pi.
I don’t want four development methodologies.
I want all of them entering the same project and asking the same basic questions:
What are we trying to achieve?
What do we know?
What do we merely believe?
What is uncertain?
What must survive?
What is the smallest useful experiment?
What evidence would actually change our mind?
And what should the next actor not have to rediscover?
That is the experiment.
No grand new lifecycle.
No claim that I’ve finally solved software development after everyone else somehow missed it.
Quite the opposite.
Most of it feels suspiciously like going back to what good developers were doing decades ago.
Small steps.
Evidence.
Loose coupling.
Refactoring.
Production feedback.
Changing your mind when reality disagrees.
Making bad decisions cheap to undo.
The tools have changed beyond recognition.
The actors are changing too.
But the engineering problem is stubbornly familiar.
Build something useful.
Learn quickly.
Preserve what reality has taught you.
Make it cheap to change your mind.
And, for the love of all that is holey:
Don’t confuse a green build with understanding.