Close your eyes and imagine the perfect codebase to work on. I bet you’ll say it has complete test coverage. It’s fully-optimized, both in terms of performance and architectural design. And, of course, it contains only DRY code. Surely we can all agree that this is an aspirational situation. But...do we really want that?
Don’t get me wrong; these qualities are all beneficial. However, if we also think we should value everything in moderation, when should we push back on these ideals? What problems can they introduce? Let’s talk about the exceptions to some of the “rules” we all hold dear.
This talk will not argue against any of the principles of adequate test coverage, optimized code, or DRY code in the broad sense. The intention is not to dissuade the audience from using any of these rules, principles, or guidelines. They’re all incredibly valuable, and we, as craftspeople, are better off for having them overall. This talk will discuss the ways these important ideals can fall short in specific ways and what alternatives are available in those situations.
The talk will initially introduce each of the principles, talking about why it’s important and what benefits it provides. Code examples will be used to illustrate how we can write code or architect software solutions that adhere to these rules. Those examples will then be used to demonstrate some shortcomings of the principle, or situations where following the standard could have negative consequences. Along with identifying some of the downsides, possible solutions and alternatives will be suggested.
This is particularly helpful in tests. If common setup about the state of the world has been extracted into a central location, it may be more difficult to tell the story of the test and understand why the current state is important or relevant to the situation being tested. Repeating this setup can draw attention to what’s relevant in the test and why.
If it’s critical to the value of your test that a struct has an attribute called “foo” which returns “true”, but that struct is defined in a helper method at the very top of the test file, it can be difficult to parse why the test is reacting the way it is. Repeating the definition of that struct closer to the exercise section of your test can improve clarity and understanding.
Explicitly duplicating functionality or sections of code for additional use, even when you feel confident it’s the same, can have many benefits. If you’re explicitly rewriting it, rather than a copy and paste operation, you can re-explore the implementation and may discover a different solution to the same problem. Even without rewriting it, having the same code multiple places can help you move faster initially without needing to consider the various call sites that a DRY solution may require. It can validate your assumptions regarding just how similar these use cases really are. This could prevent an incorrect abstraction from being chosen, and the two data points can help better inform the design decisions once that third occurrence of the functionality comes up.
Quality code is highly valued, but difficult to measure. And though we, as a community, have many ideals, they don’t all fit in every scenario. You may be hard-pressed to find a Ruby developer who might take the position that testing is bad, or DRY code is problematic. Most times, they’d be right not to take that stance. However, writing our code in service of these ideals without considering their implications has costs that may not be explored or understood at the time.
I’ve had the fortune (or misfortune) of seeing the unintended consequences of adherence to some of these rules on a team, codebase, and a project. Many of these principles can be easy to spot and follow, leading to comments that are easy to make in a pull request, changes that are easy to make, and refactorings that feel straightforward and successful enough in that time. The downsides to these changes can be more difficult to identify.
I will share examples of where, particularly in the long-term, following these rules can have negative effects in specific situations, and propose suggestions to address those effects or prevent them from occurring in the first place.