En kodare

Anders Hovmöller
About Blog Apps

When DRY fails


Don’t Repeat Yourself, or DRY, is one of the core principles of programming. It’s part of what makes software powerful: it’s avoiding duplication of effort when writing the code, but it also makes sure that when a bug is found it’s fixed everywhere. It also applies to data: if you need to change something, you want to be sure you’ve changed it, and not just one copy (this is sometimes called “single source of truth”). It’s generally agreed to be A Good Thing (it can be misused, but let’s leave that for now).

But what if you can’t be DRY?

There are many situations where DRY just isn’t feasible. Here are some:

Whatever the reason may be, it happens and it’s not always possible to avoid repeating yourself.

The problem with DRY as a principle is that it lacks an else clause. It’s like:

if feasible:

This type of code should make you go WAT, but when it’s taught in school in the form of English we often miss the glaring hole in the principle: what about the else clause? What do we do when the DRY principle can’t be applied?

DRYER (DRY Else Reconcile)!

So let’s fix the code:

if feasible:

When DRY fails, you reconcile. The simplest form of a reconciliation is to just write a test that reads data A and data B and fails with a diff if they are out of sync. The data doesn’t need to be identical (and probably can’t be, otherwise it wouldn’t be a DRY violation in the first place), so the test will need to transform A and B into some common format to be able to compare.

Notice how the last paragraph always talks about data, never code. It’s because code is a pain to reconcile, but data is fairly easy. If your code isn’t declarative when you have a DRY violation you might be in trouble. Reconciling code can be having a great test suite (100% coverage, 100% mutation tested ideally) that you can run on both pieces of code.

These are not reconciliations

I’ll start with some things that are not valid reconciliation methods.

What is reconciliation?

Reconciliation is:

Partial reconciliations where you’re missing one or more of the points above can still be hugely useful of course. Start small!

Blame the system, not the user

Every time you make a code change and forgot to update some other place that needs to be updated in sync, don’t think “oh silly me, I forgot” but instead “it’s bad that the compilation/tests didn’t fail”. The same goes for data. This type of thinking takes practice, but I think it’s generally a good idea to defaulting to blaming the system when the user screws up. This attitude makes for good systems without lots of sharp edges in the long run.

« Good Workflows 🏈 Unit tests are nice... but WHICH unit? »