At Agile Tour Vienna 2025, Dave Farley walked onto the stage with a provocation dressed up as a question: what if everything we’ve been calling “agile” is really just a craft version of something older, more rigorous, and more powerful? What if the thing that comes after agile – the discipline that survives the brand collapse and the methodology fatigue – is simply engineering done properly?
Farley has been making this argument for years, most visibly in his book Modern Software Engineering and through his widely followed YouTube channel. But hearing it delivered in a room full of practitioners – people who’ve lived through the agile gold rush, the scaling frameworks, the ceremony creep – gave it a particular weight. This wasn’t a theoretical critique. It was a practical case for what to do instead.
Farley opens with a definition he chose carefully and defends word by word: engineering is the application of an empirical scientific approach to finding efficient solutions to practical problems.
This matters because the software industry has developed two convenient but wrong ways of using the word. The first treats engineering as a synonym for bureaucracy – the thing that produces 2,000-page requirement specifications and change approval boards and processes so heavy they make the work impossible. The second treats it as simply a synonym for programming – I write code, therefore I am an engineer.
Farley is arguing for something that is neither of these things. The mental model he offers is worth holding onto: don’t picture someone fixing a car. Picture the team building a Mars rover. A machine that has to survive launch, travel through space, land on another planet, and then operate autonomously in an environment that can’t be predicted in detail in advance. That’s the kind of engineering he’s talking about. Not perfect specifications delivered upfront – disciplined, iterative, feedback-driven problem-solving under conditions of genuine uncertainty.
Margaret Hamilton understood this. She led the team that built the flight control software for the Apollo space program, and she’s credited with coining the term “software engineering” – insisting that what her team was doing deserved to be taken as seriously as any other engineering discipline. That was in the 1960s. Most of the industry is still catching up.
One of the most important reframings in the talk concerns the goal of engineering itself. We tend to assume its job is to find the one correct answer – the perfect architecture, the ideal process, the solution that will finally scale. Farley argues that’s exactly wrong. The job of engineering is not to identify the right answer. It’s to rule out the wrong ones. We don’t put jet engines underneath aircraft wings where they can touch the ground because that might cause fires. We measure bridge components precisely so the pieces actually connect. We stop doing the dumb things. That’s it. And that frame – engineering as the systematic elimination of bad ideas – is both more honest and more useful than the fantasy of upfront perfection.
The most clarifying idea in the talk is one that sounds simple once you hear it, but has significant implications for almost every structural decision a software team makes.
We treat software development like manufacturing. We build processes that resemble production lines – ordered stages, hand-offs between specialists, quality gates at the end. Waterfall development is the most obvious version of this, but the instinct persists in more subtle forms across many teams that would never call themselves waterfall.
The problem is that manufacturing is solving a different problem to the one we face. If you’re building physical objects – bottles, engines, aircraft – the act of construction is genuinely the hard part. Materials have to be sourced, shaped, assembled, tested. Producing one unit is expensive and time-consuming. Producing the next unit costs almost as much. The production problem is real.
Software doesn’t work like that. Software is, as Farley puts it, crystallised ideas. You write ideas down in the form of code. You run that code through a compilation process and you end up with a stream of bytes. And a stream of bytes – regardless of how large or complex the system it represents – can be cloned perfectly, indefinitely, for essentially zero cost. Production, in the traditional sense, is a solved problem. You press a button.
What this means is that our problem is always the design step. The thinking step. The figuring-out-what-we-actually-want-and-how-to-build-it step. That work is always new, because if we’d already solved the problem we’d just copy the solution. We are always in the business of learning. And if that’s true, we should be world-class at learning. That should be the foundation our whole discipline is built on. Most teams don’t think about it that way round. Most teams are still trying to solve the production problem they don’t have.
If learning is the job, what does it mean to do it well? Farley offers five principles, each grounded in examples from outside software that make the ideas concrete and memorable.
Iteration is the most powerful problem-solving tool our species has. The Wright Flyer’s first flight in 1903 lasted less distance than the wingspan of a modern jumbo jet. By 2017, commercial aviation flew the equivalent of two-thirds of the global population in a single year with zero passenger fatalities. The distance between those two data points is iteration – relentless, feedback-driven improvement. The key insight is that you don’t need to know the destination precisely. You need a starting point, a direction, and a way to measure whether each step is moving you closer or further from the goal. Everything else follows from there.
Feedback is what gives iteration its power. Without a signal telling you whether you’re improving, iteration is just repetition. Farley’s illustration here is one of the clearest in the talk: imagine you’re asked to balance a broom. You could approach it mathematically – calculate the center of mass, determine the exact point of contact needed, position everything with precision. That approach works, after a fashion, but it produces exactly one solution and it’s fragile. Any disturbance breaks it. The way every human being actually balances a broom is by putting it on their hand and wobbling – sensing which way it’s tilting, moving to counteract it, making continuous micro-corrections. That feedback-driven approach has an essentially infinite number of solutions and is robust to disruption. The lesson for software: fast, clear feedback is the single change most likely to improve the quality of what you build. It’s what continuous integration is about. It’s what TDD is about. It’s what retrospectives are about. Optimise for it ruthlessly.
Incrementalism is related to iteration but distinct from it. Iteration is about improving something that exists. Incrementalism is about building something in stages so that each stage is valuable, learnable, and changeable. The International Space Station is Farley’s example: it was launched as a single module and expanded over many years, with components added that hadn’t even been conceived when the first piece went up. That’s what incremental development preserves – the ability to learn, change your mind, and incorporate discoveries that happen during the work itself. Big-bang releases don’t allow for this. They assume all learning happens upfront. That assumption is almost always wrong.
Experimentation is not a nice-to-have. It is the only mechanism by which genuine progress is made. Farley is blunt about this: if you can’t afford to run experiments, you can’t afford to do anything, because there is no other way of finding out what actually works. He cites Jeff Bezos’s approach at Amazon: the company actively tracks how many of its experiments fail and targets 50%. Not because failure is the goal, but because a failure rate lower than that suggests the team isn’t being ambitious enough. When an experiment confirms your hypothesis, you haven’t learned anything – you started with the right answer. You learn when things go wrong, in ways you didn’t expect, and force you to update your understanding. The aviation industry perfected this principle: you don’t put a new engine on a new airframe and hope for the best. You put the new engine on an old airframe, fly it, and see what happens. Then you put the old engine on the new airframe. Then you put both together. Control your variables. Learn from each step.
Empiricism completes the picture. Where experimentation is about controlled tests, empiricism is about learning from reality even when it surprises you – especially when it surprises you. The first production jet aircraft flew with landing gear designed to accommodate a propeller that was no longer there. The jets pointed at the ground. The engineers melted the runways on the first few flights. Nobody had predicted this. It wasn’t in any specification. The fix – repositioning the undercarriage so the aircraft sits level and the engines point away from the ground – became the standard design for almost every jet ever built. Unintended consequences are where the real learning happens. Being empirical means remaining genuinely open to them.
The second half of the engineering challenge is complexity. Modern software systems are, by any reasonable measure, beyond what any individual can hold entirely in their head. Farley uses an animation of AWS microservices – a dynamic, constantly-changing web of services and connections – as his illustration. This isn’t understandable by inspection. We need design disciplines that contain and constrain complexity, so that it can be navigated and modified without the whole thing collapsing.
Modularity means building components that have clear, independent functions and can be developed, understood, replaced, and tested separately. The printer driver is Farley’s example – and it’s worth pausing on, because it’s easy to take for granted. There was a time when if you wanted your software to print, you had to know exactly which printer it would use and write code specific to that hardware. The abstraction of the printer driver – where your application says “I want to print this” and delegates everything else – was a deliberate design decision that someone had to make. It seems obvious now. It wasn’t always.
Cohesion is the counterpart to modularity: putting related things close together. Kent Beck’s formulation is both slightly funny and exactly right – move things that are related closer to each other, and things that aren’t related further apart. Apply this at every level of scale: architecture, module, class, function, variable. The discipline is the same regardless of the scope.
Separation of concerns is the practice that tends to drive both modularity and cohesion. One method, one thing. One class, one responsibility. BMW’s electric vehicle strategy offers a useful physical analogy: they’ve separated the driving platform – batteries, motors, steering, brakes – from the vehicle body entirely. The same platform can sit under an SUV, a saloon, or a sports car. The concerns are genuinely separate, and each can evolve independently.
Abstraction is how you hide complexity behind clean interfaces. You don’t know how your car’s steering wheel works – whether it’s mechanical, hydraulic, or electronic – and you don’t need to. The problem of steering has been abstracted completely away from the driver. Good software design works the same way: each part of the system keeps secrets from the other parts, so that changes in one place don’t cascade unpredictably through everything else.
Coupling is the enemy of all of the above. Farley’s Lego analogy is the most memorable moment in the talk on this point. Classic Lego bricks connect through a generic contract: any brick fits any other brick, in almost any configuration. A licensed Ferrari kit connects in exactly one way. Both are valid for their purpose. But software almost always needs the generic contract. High coupling means that a change in one place requires changes everywhere else. It makes the codebase fragile, the tests slow, and the team afraid. Reducing it is one of the most reliably impactful things a development team can do.
The talk ends with a live demonstration, and it reframes something many experienced developers think they already understand about test-driven development.
TDD is typically described as a testing technique. Farley argues it’s actually a design technique – one that, when applied consistently, forces all the right engineering decisions without requiring you to make them consciously.
The demonstration uses a simple Car class. Without TDD, you write the car, it contains an engine, and everything is tangled together. With TDD, you write the test first. And the moment you try to write a test for the car in isolation, you discover that you need to inject the engine as a parameter – otherwise you can’t control it in the test. That one constraint, driven entirely by the desire for testability, produces: modular code (car and engine are now separate), better abstraction (the engine is a parameter, not an implementation detail), improved cohesion (car concerns stay in the car class), and reduced coupling (you can pass in any engine – petrol, electric, jet – without changing the car at all).
None of that required a design decision. It emerged from the discipline of writing the test first. The argument for TDD, properly understood, is not primarily about catching bugs. It’s that the practice mechanically constrains you toward the design properties that make software easy to change.
Farley’s closing thought is the one he most wants the room to carry away. Software quality, properly understood, is not about security, or scalability, or reliability, or any specific property. Those things matter enormously. But there is one property on which all of them depend: how easily can you change the software?
If you can’t change it, you can’t fix its security vulnerabilities. You can’t improve its reliability. You can’t scale it in the ways your users eventually need. The only path to all of those properties, durably and over time, is software that can evolve – software built on the ten principles this talk is built around.
And this, Farley notes, applies with equal force in the age of AI-generated code. When an AI writes the code, the engineering discipline doesn’t become less relevant. It becomes more relevant. The bottleneck shifts from writing code to specifying code correctly – and the quality of the specification, the clarity of the constraints, the rigour of the test that drives the design, becomes the primary determinant of quality. The principles don’t change. The importance of understanding them only grows.
If this talk resonated, Dave Farley’s book Modern Software Engineering and his YouTube channel are both worth your time.