UPDATE: This post was so popular that I recorded a quick podcast about the reception and added a bit more color to my opinions. Listen here: https://thedailydeveloper.substack.com/p/is-duplication-complex
Non-DRY specs are more maintainable because they are an excellent example of choosing duplication over the wrong abstraction, of writing code that posesses locality instead of compression, code that is simple instead of complex.
Got it?
I was walking around the neighborhood today and heard, to my pleasant surprise, a familiar voice on The Maintainable Software Podcast: Jeanine Soterwood!
Jeanine and Robby were talking about non-DRY specs:
I was working on a spec and had my usual let statements at the top of the file..one of the developers just asked nicely, "would you consider defining all of the variables within the spec itself (and removing the let statements)? Once I started doing that, I thought: I love this. I feel like it is so much easier to understand the setup and teardown of a spec. And when you do have to go back and look at code you haven't looked at in awhile, everything is right there. I also work on one of the projects for Ruby For Good. There are a lot of new developers that come to that project who are often new to writing tests. It's so much easier for them if all of the setup and teardown is in that one test. It's much easier to follow and much easier to write the tests as well - they ask themselves what objects they need and just create them. Even though sometimes it is hard - you're repeating yourself many times because your tests are similar and you're repeating blocks, I really love it.
I am now a total convert to the non-DRY tests.
Thoughtbot even has a blog post about this topic.
What are non-DRY specs?
DRY (Don’t Repeat Yourself) specs are sorta the default: when you find yourself repeating setup steps in your test cases, extract them into `let` statements in the setup/before block.
Non-DRY Specs, then, are test cases in which you resist the urge and instead keep duplicated setup steps, specifically variables, in each test cases.
Removing abstraction
You see, I’ve heard this story before. Sandi Metz famously quipped:
Duplication is better than the wrong abstraction
You can read her great blog post where Sandi expounds.
The basic idea is that the wrong abstraction is costlier to maintain in the long run than duplicate code. This is one of those things that may sound a little crazy - isn’t removing duplication the entire point of programming?
First you have to acknowledge that abstractions are hard to get right. Just because you can, doesn’t mean you should. You have to acknowledge that there is a cost associated with extracting duplicated code into an abstraction. Specifically, it’s the cost of obtaining the context upon reading it.
Richard Gabriel calls this characteristic compression:
Compression is the characteristic of a piece of text that the meaning of any part of it is "larger" than that piece has by itself.
Compression in object-oriented languages is created when the definition of a subclass bases a great deal of its meaning on the definitions of its superclasses.
Compression is dangerous because it requires the the programmer to understand a fair amount about the context from which compressed code will take its meaning.
Maintaining compressed code requires understanding its context, which can be difficult.
The primary feature for easy maintenance is locality: Locality is that characteristic of source code that enables a programmer to understand that source by looking at only a small portion of it.
Compressed code does not have this property.
Simplicity
For years I've referenced Rich Hickey's famous description of simplicity, and how it differs from cardinality, from his talk Simple Made Easy:
SIMPLE:
One fold/braid
One role
One task
One concept
One dimension
But not
One instance
One operation
About lack of interleaving, not cardinality
He also says:
Complex things must be considered together
Maintenance
And, Richard Gabriel writes (in 1996!) about the difference between compression and reuse, in his excellent book Patterns of Software:
Most people agree that maintenance is a big part of the overall software problem, but to organizations whose survival depends on getting out new projects or products, the important issue is getting the new software done.
Habitability is the characteristic of source code that enables programmers, coders, bug fixers, and people coming to the code later in its life to understand its construction and intentions and to change it comfortably and confidently.
Software has to be habitable because it always has to change.
If software will need to change, it needs to be habitable, to have locality. If it has a poor abstraction or is compressed, it resists change.
Do you see the connections?
Abstraction is the process of identifying common patterns that have systematic variations; the abstraction represents the common pattern and provides a means for specifying which variant to use
Locality: Code that is easy to grok because it does not rely on larger context elsewhere
Cardinality: Having more than one thing
Complexity: Having multiple things intertwined with each other
Duplication: The same code, or similar code, in more than one place
Compression: Code that requires context to understand and change confidently
To synthesize:
Simple is habitable. Simple is maintainable.
Duplication is simple
Locality is simple
The wrong abstraction is complex
Compression is the wrong abstraction. Compression is complex.
And to bring it back:
Non-DRY specs are more maintainable because they are an excellent example of choosing duplication over the wrong abstraction, of writing code that posesses locality instead of compression, code that is simple instead of complex.
Got it?
> simple instead of complex.
This is the key, in the end all that matters after code correctness is code readability and maintainability this is the aim.
All these patterns can only help in achieving this aim or sometimes even hinder if not used where it fits.
Applying patterns only for the sake of it is done only by inexperienced programmers and many can can stay on this level even after years of "experience".