Posted in

Software Development Comes Down to Three Principles

The life of an average software developer is a constant bombardment of information.

Every day, every hour, there is a new pattern, a new approach, a new technology, or a new discipline waiting to be learned. We try our best to learn them, understand them, and put them into practice.

But this bombardment is so intense that we often get lost in it. We learn more and more things, yet struggle to organize what we know and understand how all these pieces fit together.

Is There a Big Picture in Software Development?

This is a question many of us never really think to ask.

We tend to accept software development as inherently complex — or perhaps as a kind of organized chaos. The idea of reducing it to something simpler in order to understand it better rarely crosses our minds.

In this article, I want to approach software development from a particular perspective and show how I believe much of it can be built upon three fundamental principles.

Three principles that, in my view, sit at the very foundation of software development.

So yes, there is definitely a big picture in software development.

In fact, depending on where you look from, you can draw different big pictures. From a product perspective, you will see one. From an architectural perspective, you will see another. They are all important, valuable perspectives worth understanding.

But in this article, I want to look at software development from a different angle.

I want to talk about three fundamental principles that may appear technical at first, but whose implications go far beyond the technical side of software development. These are the principles upon which I have built my own understanding of software development.

And I consider all three indispensable.

I will explain what they are, why they matter, and why I believe so many of the practices, patterns, methodologies, and ideas we use in software development can ultimately be traced back to them.

Because, in my view, software development is, before anything else, an attempt to achieve three things:

Feedback

Software development is built on assumptions.

We assume that we understood the problem correctly. That our design will solve it. That the code does what we intended. That different parts of the system will work together. That production will behave as expected.

And most importantly, that what we are building is actually valuable.

Making assumptions is not the problem.

The problem begins when those assumptions survive for too long without being challenged.

That is why feedback is fundamental.

Feedback is the mechanism through which reality tells us whether our assumptions were correct.

And many of the most important practices in modern software development can be seen as attempts to shorten that feedback loop.

A compiler tells us within seconds that something is wrong.

Tests tell us whether expected behavior still works.

Continuous Integration tells us whether our change works with everyone else’s.

Code review gives us another engineer’s perspective.

Observability and monitoring tell us what is actually happening in production.

And users tell us whether we solved the right problem in the first place.

Different mechanisms, same underlying goal:

Reduce the distance between an action and the information that tells us whether that action was correct.

The shorter that distance becomes, the safer it becomes to change the system.

This is why iterative and incremental development works so well. Instead of making hundreds of decisions and discovering months later whether they were right, we make smaller decisions, observe the results, learn, and adjust.

Software development is therefore less about predicting everything correctly upfront and more about discovering mistakes quickly.

We cannot eliminate uncertainty.

Requirements change. Our understanding changes. Technology surprises us. Users surprise us. Production surprises us.

What we can do is shorten the time between being wrong and discovering that we are wrong.

That is why feedback is not merely a testing principle.

It is a fundamental software development principle.

But feedback creates another problem.

Once we learn that something needs to change, how easily can we change it?

That brings us to simplicity.

Simplicity

Software naturally tends toward complexity.

Every feature adds behavior.

Every integration adds dependencies.

Every exception adds another path through the system.

Every abstraction adds another concept someone needs to understand.

And software allows us to add these things almost without friction.

One more class.

One more service.

One more layer.

One more framework.

One more configuration option.

Until the system becomes difficult to understand and even harder to change.

This is where I believe an important distinction needs to be made:

Complex problems do not necessarily require complicated solutions.

Our job is not to reproduce the complexity of the problem inside the code.

Our job is to manage it.

That is what simplicity is about.

Not simplistic software.

Not avoiding necessary complexity.

But removing unnecessary complexity.

Every unnecessary abstraction, dependency, layer, service, configuration, and line of code increases the amount of information someone must understand before safely changing the system.

That is why so many software practices point us in the same direction.

KISS tells us not to make things more complicated than necessary.

YAGNI tells us not to build for hypothetical futures.

Refactoring removes accidental complexity.

TDD encourages us to solve the problem in front of us instead of designing an imagined future.

Iterative development encourages us to solve smaller problems rather than everything at once.

The reason is simple.

Everything we introduce today may need to be understood, tested, debugged, maintained, migrated, and eventually removed tomorrow.

Simplicity therefore has a compounding effect.

A simpler system is easier to understand.

An easier system to understand is easier to change.

An easier system to change allows us to respond to feedback faster.

And faster feedback means we need fewer speculative decisions upfront.

But simplicity eventually hits a limit.

Software grows.

A system that begins with a handful of concepts may eventually contain thousands.

At that scale, “keep it simple” is no longer enough.

We need a way to preserve simplicity while the system grows.

That is where Separation of Concerns enters the picture.

Separation of Concerns

If I had to choose the single most important principle of software design, it would be Separation of Concerns.

Because once a system becomes large enough, we cannot make the whole thing simple.

What we can do is make each part simple in isolation.

Human beings cannot reason effectively about hundreds of interacting concepts at the same time.

So we divide a large problem into smaller ones and create boundaries between them.

Inside each boundary, we focus on one concern while temporarily ignoring the rest.

That is Separation of Concerns.

And once you start looking for it, you see it everywhere.

Functions separate small pieces of behavior.

Modules separate areas of responsibility.

Interfaces separate what something does from how it does it.

Layers separate business logic from infrastructure.

Bounded Contexts separate one domain model from another.

Hexagonal Architecture separates the core domain from external technology.

Services can separate independently evolving capabilities.

The scale changes.

The principle does not.

This is also why cohesion and coupling matter.

High cohesion keeps related things together.

Low coupling keeps unrelated things from knowing too much about each other.

Encapsulation, modularity, dependency inversion, Clean Architecture, DDD, Ports and Adapters — they all approach the same fundamental problem from different directions:

How do we stop unrelated concerns from becoming entangled?

Because when concerns are mixed together, change spreads.

A business rule affects the database.

A UI change breaks domain logic.

A change in one service forces changes in five others.

A local modification produces unrelated failures.

Change has no boundary.

Good separation creates boundaries around change.

Ideally, when one concern changes, the impact remains mostly inside the area responsible for it.

And this brings us back to simplicity.

A well-separated system allows us to reason about one part without understanding everything else.

Instead of one enormous complicated system, we deal with many smaller and simpler ones.

That is how Separation of Concerns allows simplicity to survive at scale.

And it strengthens feedback too.

Smaller, well-separated components are easier to test, easier to reason about, and easier to change safely.

The three principles are starting to connect.


Feedback, Simplicity, and Separation of Concerns may initially look like three independent ideas.

I do not think they are.

I think they form a system.

We need Feedback because software development is full of uncertainty.

Feedback tells us when something needs to change.

But responding to feedback requires changing the software.

And change becomes harder as complexity grows.

So we need Simplicity.

Simplicity makes software easier to understand and change.

But as the system grows, global simplicity becomes impossible.

So we need Separation of Concerns.

Separation of Concerns breaks a large system into smaller areas that can remain understandable independently.

Which makes change easier.

Which allows us to respond to feedback faster.

And the loop begins again.

Feedback enables learning.

Simplicity enables change.

Separation of Concerns enables simplicity to survive at scale.

That is the relationship I see between them.

A Different Way to Look at Software Practices

This perspective also gives us a way to organize the endless number of concepts we encounter in software development.

Instead of memorizing each one as an isolated practice, we can ask:

What fundamental problem is this practice trying to solve?

TDD? Feedback, while also encouraging simplicity.

Continuous Integration? Feedback.

Observability? Feedback from production.

Refactoring? Simplicity.

YAGNI and KISS? Simplicity.

Encapsulation and modularity? Separation of Concerns.

Clean Architecture? Separation of Concerns.

Domain-Driven Design? Separation of Concerns combined with continuous feedback from the domain.

Pair Programming and Code Review? Feedback.

Agile? Shorter feedback loops, smaller increments, and less unnecessary work.

Suddenly, the landscape looks a little less chaotic.

The practices and patterns do not become less important.

They become easier to place.

Instead of seeing hundreds of unrelated ideas, we begin to see different tools attacking the same fundamental problems from different angles.

And that, to me, is what a useful big picture should do.

It should not contain everything.

It should help us understand where everything belongs.

Thanks for reading 🙂