The opposite of DRY isn't WET, it's YAGNI

November 16, 2024
Santiago, Chile

View on Threads

Ok, that's a lot of acronyms. Let me break down what I mean:

Writing software inherently involves resolving ambiguity. Often, there are two competing forces and you need to strike a balance. Knowing how to make such a tradeoff comes from accumulating experience, from developing taste. A tradeoff I often balance is between writing DRY code and admitting that YAGNI.

When I was in my first internship at the end of college, my friend who got me the job was a big proponent of DRY. Once I learned the definition, writing code any other way seemed totally dumb. "The whole idea of programming is to automate drudgery," I remember another colleague telling a more impressionable me. "Never write anything twice!" I nodded along, enlightened.

I remember reading The Elements of Style in high school, feeling empowered with all sorts of tactics to express thoughts in the written word better. Many years later, I would read about a published author on social media who once loved Elements too. He said it took him years of writing to unlearn its lessons.

You can't edit what you didn't write. You can't optimize code that's not written yet. DRY feels like a distraction from solving the actual problem.

My spouse, reacting to the first draft of this article.

In the same vein, it took me years to unlearn writing DRY code. To truly not repeat yourself, you have to have knowledge of the future. And, if anything is constant in software, it's change. More often than not, the abstractions you create will be wrong, just as your guesses about the future are wrong. In this abstraction's wake, you're left with technical debt.

Prediction is hard – especially of the future!

Niels Bohr

But abstracting feels good. We really do get into software to automate away drudgery! The tools you learn in college or early in your career – OOP, Design Patterns, SOLID Principles, etc. – are totally fun to wield. These all definitely have a place and time. In my opinion, that time is usually later on.

If there is any lesson that I learned in my last job, it's this: before introducing an abstraction, wait until you have a lot of repeated cases — say, 5 or 6 instances — to really know the scope of what you need to generalize. In the present moment, it actually can be totally OK to write WET code. It's nearly always easier to refactor something than it is to anticipate the right abstraction up front. Not to mention, the abstractions you add late will often lead to more performant, not just more maintainable, programs.

There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies, and the other way is to make it so complicated that there are no obvious deficiencies. The first method is far more difficult.

C. A. R. Hoare

While I believe I am a "good guesser" and can introduce good abstractions, I find I write less complicated code when I react instead of predict. In many ways, I find abstracting early a la DRY is a flavor of premature optimization. Whenever I feel tempted to add an abstraction early, I tell myself: "You Ain't Gunna Need It."

Say No To Classes and Yes To Types

Wow, I love this idea! Here is another for your consideration (if you’ll permit a tech talk).

I call this pairing “say ‘no’ to classes and ‘yes’ to types.”

youtu.be/o9pEzgHorH0?...

kobzol.github.io/rust/python/...

[image or embed]

— Al Merose ( @alexmerose.com) November 12, 2024 at 9:54 AM

If software is about building taste in the face of ambiguity, here's a specific preference that I've developed. It is a heuristic for how I trade off between DRY and YAGNI. Whenever I get the chance, I say "No" to classes and "Yes" to types. Two specific artifacts from members of the Python community inform this perspective.

The first is Jack Diederich's now classic talk, "Stop Writing Classes". It really is worth a watch. I have seen no better embodiment of the Zen of Python. (If the Zen of Python is unfamiliar to you, I recommend running this within a Python REPL:

import this

)

I find this talk really inspirational. It has helped me unlearn route habits from a formal CS education emphasizing Java (and Java 1.6 at that). Since watching, I've taken a critical look at my programming habits. When I find myself reaching for a class, I remind myself that probably YAGNI, and I write a function instead. More often than not, I say "No" to classes.

Since this talk came out, however, the Python language has changed a lot. More and more, the community is embracing typing and other formal methods for program correctness. This blog post from Jakub Beránek (aka @Kobzol), "Writing Python like it's Rust" is emblematic of this trend.

The lesson to not make error states representable in your programs is well worth abstracting up-front to me. Algebraic Data Types and other means of using types for program correctness, to me, are well worth their effort. Even though it may mean generalizing early, I often say "Yes" to types.

These two heuristics — YAGNI over DRY, Types over Classes — have served me well. I may yet throw these patterns out the window later as I continue to grow. I dare say that you, dear reader, should adopt these preferences too, but then I'd be thinking for someone else.