Rick Astley accidentally wrote one of the best software engineering principles in 1987.

Never gonna give you up
Never gonna let you down
Never gonna run around and desert you

That is basically the contract every piece of software makes with the code that depends on it and much like most promises in software, we break it constantly.

I call this the Rick Astley Principle of Software Engineering:

Good software should never unexpectedly give up on its consumers, let them down, or disappear when they depend on it.

This sounds stupid enough to be memorable, which already puts it ahead of half the principles we use in software development.

Never Gonna Give You Up

Imagine you install a library today.

services.AddSomethingUseful();

It works perfectly. You ship it. Six months later you upgrade to the latest version and half of the API has disappeared.

The method is gone. The configuration model has changed. Some property that used to accept a string now wants an IOptions<SomethingOptionsFactoryContext>.

The release notes say:

We’ve simplified the developer experience.

You spend the rest of the afternoon simplifying your way through 47 compiler errors.

Once people depend on your software, you have made a promise whether you intended to or not.

Public APIs are promises. Database schemas are promises. CLI arguments are promises. Configuration keys are promises. Even weird behavior that you accidentally shipped three years ago can become a promise if enough people depend on it.

That doesn’t mean software can never change. It means change has a cost, and you shouldn’t casually transfer that cost to everyone using your code.

Sometimes keeping an ugly API around is better engineering than replacing it with a beautiful one.

Never Gonna Let You Down

Every system fails.

Servers crash. Networks disappear. DNS decides it needs some personal time. Databases get overloaded. Someone deploys something at 16:55 on Friday.

You cannot promise that software will never fail.

You can promise that it won’t fail like an idiot.

There is a huge difference between this:

Request failed.

and this:

Payment provider returned HTTP 503 after 3 attempts.
Correlation ID: 8f3c...

One tells you something went wrong.

The other gives you a chance of fixing it before someone starts restarting random services in production.

Good software fails predictably. It gives useful errors. It exposes metrics. It respects cancellation. It times out instead of hanging forever. It doesn’t retry something 14,000 times because somebody added Polly and assumed more retries meant more resilience.

Reliability isn’t the absence of failure. It’s how your system behaves when failure inevitably happens.

Never Gonna Run Around and Desert You

There is another kind of software failure that doesn’t involve an exception.

Abandonment.

Every company has that one internal service.

Nobody knows who owns it.

The repository says it was last meaningfully changed in 2021. The README contains setup instructions for Visual Studio 2017. There are three people in the CODEOWNERS file and none of them work at the company anymore.

But the service processes €4 million worth of transactions every month, so nobody is brave enough to touch it.

This is where software engineering stops being about writing code and starts being about ownership.

If something matters enough to run in production, somebody should know they are responsible for it.

Who gets alerted when it fails? Who reviews dependency upgrades? Who knows why it exists? Who decides when it should be retired?

“Dave wrote it” is not an ownership model.

Especially when Dave left four years ago.

Never Gonna Make You Cry

Breaking changes deserve their own category because they have probably caused more developer rage than merge conflicts and YAML combined.

Sometimes breaking changes are necessary. Bad APIs shouldn’t live forever just because somebody released version 1.0 too early.

But there is a massive difference between:

We are removing this API tomorrow.

and:

This API is deprecated. The replacement is available now. Both will work for the next two major versions. Here is a migration guide.

The second approach costs maintainers more.

That is the point.

Compatibility shifts work from your users to you.

And if you maintain a framework, package, API, or platform used by thousands of developers, that is usually where the work belongs.

Your users shouldn’t need to schedule a sprint because you decided a method name wasn’t aesthetically pleasing anymore.

Never Gonna Say Goodbye

Developers love deleting old code. There is something deeply satisfying about removing 8,000 lines and watching the solution still compile.

The problem is that old code often exists for reasons nobody remembers. You find this:

if (customer.Country == "DE")
{
ApplyLegacyCalculation();
}

Obviously terrible. You delete it.

Two weeks later somebody from finance asks why German customers have been charged incorrectly since Tuesday.

Congratulations! You have discovered documentation through production incident. Old code is not automatically bad code. Sometimes it is code that survived long enough to become invisible infrastructure.

Before deleting something strange, work out why it exists. git blame is not just for finding somebody to complain about (and trust me I used it for complaints).

Sometimes the weirdest line in the codebase is holding the entire business together.

Never Gonna Tell a Lie and Hurt You

Software lies to developers all the time.

bool isSuccess;

What does success mean?

HTTP 200? Database write completed? Message published? Payment actually settled?

Or my personal favorite:

catch (Exception)
{
return true;
}

Technically successful. The worst abstractions are not the complicated ones. They are the ones that look simple while hiding important behavior.

A method called SaveAsync() should probably save something before it returns.

A method called GetUser() should not make six HTTP calls, publish a Kafka message, update a database record, and sacrifice a goat.

Names are contracts too.

When code says one thing and does another, every developer using it has to build a mental model of the lie. Eventually somebody gets it wrong.

The Actual Principle

Software engineering is largely about not surprising the people who depend on your software.

Stable APIs matter because unexpected changes hurt consumers. Good errors matter because unexpected failures are hard to diagnose. Ownership matters because abandoned systems eventually become somebody else’s emergency. Clear naming matters because developers make decisions based on what code tells them.

The best software often isn’t the cleverest software. It is the software that behaves exactly how you expected it to.

Which brings us back to Rick Astley.

If your API won’t give me up, your service won’t let me down, your maintainers won’t run around and desert me, and your next release won’t make me cry, you’re already doing better than a surprising amount of production software.

Maybe we didn’t need another 400-page software architecture book after all.

Maybe we just needed Rick.

Affiliate promo

If you love learning new stuff and want to support me, consider buying a course from Dometrain using this link: Browse courses – Dometrain. Thank you!

Leave a comment

Trending