loading...
Cover image for DRY to the point of WET yak shaving

DRY to the point of WET yak shaving

renascent479 profile image Serena ・5 min read

Opinions expressed here are my own and do not necessarily reflect that of my employer

It's often drilled into us early in our software development tenure to adhere to DRY principles - Don't Repeat Yourself. Don't get me wrong, this is an important principle, but its purpose is misunderstood.

Lets demonstrate this by building a sample component based on this layout.

Looks easy enough. We'll be using React for fun, and assume we have all the CSS and assets as a given.

Let's get to it already!


We'll start with a single React component, and start breaking it down as needed. Going off the top of my head, we'll end up with something like this:

//HeroTracer.js
export default class HeroTracer extends React.PureComponent {
    render() {
        return(
            <React.Fragment>
                <section className="content">
                    <ul className="tabs">
                        <li>Overview</li>
                        <li>Story</li>
                    </ul>
                    <div className="content_intro">
                        <small className="text-blue">Role</small>
                        <h1 className="title_role">Offensive</h1>
                        <div className="difficulty">
                            <small className="text-blue">Difficulty</small>
                            <img src="star_difficulty.png"/>
                        </div>
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
                    </div>
                    <div className="content_abilities">
                        <h2 className="title_abilities">Abilities</h2>
                        <ul className="abilities">
                            <li>
                                <div className="ability">
                                    <img src="ability1.png"/>
                                    <span>Lorem ipsum</span>
                                </div>
                            </li>
                            <li>
                                <div className="ability">
                                    <img src="ability2.png"/>           
                                    <span>Pellentesque</span>
                                </div>
                            </li>
                            <li>
                                <div className="ability">
                                    <img src="ability3.png"/>
                                    <span>Sit amet</span>
                                </div>
                            </li>
                            <li>
                                <div className="ability">
                                    <img src="ability4.png"/>
                                    <span>Adipiscing elit</span>
                                </div>
                            </li>
                        </ul>
                    </div>
                </section>
                <aside className="hero_image">
                    <img src="tracer.gif"/>
                </aside>
            </React.Fragment>
        );
    }
}

Ever get that feeling of déjà vu?


"Oi, but this code isn't DRY!" you say.

Correct. Lets optimize it a bit. The abilities section can be generated from an array of objects. We should pull this into a new component so it can be reused elsewhere.

//HeroTracer.js
import LabeledIcon from './labeledIcon';

export default class HeroTracer extends React.PureComponent {
    componentDidMount = () => {
        this.tracerAbilities = [
            {image: 'ability1.png', content: 'Lorem ipsum'},
            {image: 'ability2.png', content: 'Pellentesque'},
            {image: 'ability3.png', content: 'Sit amet'},
            {image: 'ability4.png', content: 'Adipiscing elit'}
        ];
    }

    buildHeroAbilities = (ability) => {
        return <LabeledIcon ability={ability} class="ability"/>
    }

    render() {
        return(
            <React.Fragment>
                <section className="content">
                    <ul className="tabs">
                        <li>Overview</li>
                        <li>Story</li>
                    </ul>
                    <div className="content_intro">
                        <small className="text-blue">Role</small>
                        <h1 className="title">Offensive</h1>
                        <div className="difficulty">
                            <small className="text-blue">Difficulty</small>
                            <img src="star_difficulty.png"/>
                        </div>
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
                    </div>
                    <div className="content_abilities">
                        <h2 className="title">Abilities</h2>
                        <ul className="abilities">
                            {this.tracerAbilities.map(this.buildHeroAbilities)}
                        </ul>
                    </div>
                </section>
                <aside className="hero_image">
                    <img src="tracer.gif"/>
                </aside>
            </React.Fragment>
        );
    }
}

//labeledIcon.js
export default class LabeledIcon extends React.PureComponent {
    render() {
        return(
            <div className={this.props.class}>
                <img src={this.props.ability.image}/>
                <span>{this.props.ability.content}</span>
            </div>
        );
    }
}

Looking good. Really starting to see the usefulness of making code DRY. I wonder what else I can do.

Oh! I see <small className="text-blue"/> written twice here.

Also the <h1 className="title"/> is pretty similar to <h2 className="title"/>.

Since I'm repeating code, I should make those into components too.

//HeroTracer.js
import LabeledIcon from './labeledIcon';
import SmallBlueLabel from './smallBlueLabel';
import HeroHeader from './heroHeader';

export default class HeroTracer extends React.PureComponent {
    componentDidMount = () => {...}

    buildHeroAbilities = (ability) => {...}

    render() {
        return(
            <React.Fragment>
                <section className="content">
                    <ul className="tabs">{...}</ul>
                    <div className="content_intro">
                        <SmallBlueLabel content="Role"/>
                        <HeroHeader content="Offensive" type="1"/>
                        <div className="difficulty">
                            <SmallBlueLabel content="Difficulty"/>
                            <img src="star_difficulty.png"/>
                        </div>
                        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
                    </div>
                    <div className="content_abilities">
                        <HeroHeader content="Abilities" type="2"/>
                        <ul className="abilities">{...}</ul>
                    </div>
                </section>
                <aside className="hero_image">{...}</aside>
            </React.Fragment>
        );
    }
}

//labeledIcon.js
export default class LabelIcon extends React.PureComponent {...}

//smallBlueLabel.js
export default class SmallBlueLabel extends React.PureComponent {
    render() {
        return(
            <small className="text-blue">
                {this.props.content}
            </small>
        );
    }
}

//heroHeader.js
export default class heroHeader extends React.PureComponent {
    render() {
        const headerTag = `h${this.props.type}`;
        return(
            <headerTag className="title">
                {this.props.content}
            </headerTag>
        );
    }
}

Now I see that <div className="content_intro"/> and <div className="content_abilities"/> both have the same tag and class prefix. Lets... stop you right here and take a look at what we accomplished.

Good news: We've made our code DRY
Bad news: We've DRY-ed so far that we've gone WET - We Enjoy Typing🤷 The latter two components are, to be blunt, wasteful.

The cavalry's here!


But wait, we saw repeated code and we refactored it because of DRY principles. Why is it correct to refactor LabeledIcon but not SmallBlueLabel?

"Most people take DRY to mean you shouldn't duplicate code. That's not its intention. The idea behind DRY is far grander than that. DRY says that every piece of system knowledge should have one authoritative, unambiguous representation."
-Dave Thomas
The Pragmatic Programmer

The way we can apply that clarification of DRY to our examples is that LabeledIcon maps a dataset into a desired template. Albeit somewhat simple HTML template, but is still something that isn't natively defined by a single tag.

SmallBlueLabel repeats what semantic HTML and CSS provides. Yes, so it actually twice violates DRY principles. In the future we may have a more complex layout to necessitate it being abstracted out, but we don't know that for sure.

Don't try to save time in the future by investing in unnecessary time now. It may turn out that we may never need a more complex SmallBlueLabel.

Because we incorrectly predicted where we may have a future use, we've not only went down a path of creating unnecessary, one-off components, but future developers will have a harder time understanding the code.

Keep calm and Tracer on!


What are some takeaways from this post?

DRY is a principle, not a rule.

It's important distinguish between knowledge vs code duplication. The former is always a DRY violation, the latter, not always. Learn how to tell the two apart.

Trying to predict optimization at the start can lead to yak shaving. Get it working first, refactor later.

Describing unnecessary DRY refactorings as WET - We Enjoy Typing is funny.

Lastly, knowing the time to take a step back and consider the future implications of your code is part of the journey from a new developer to an experienced one.

P.S. Apologies if there's any syntax errors in the React code, my mental compiler isn't always reliable

Discussion

pic
Editor guide
Collapse
brunoc profile image
Bruno Carriere

Experienced programmers trend towards more and more abstraction, resulting in DRY-er code (usually).

Eventually one reaches a point when they realize where duplication makes sense, taking you back to writing code that's superficially similar to what a beginner might do, but with a better understanding of what trade-offs you make.

Trying to predict optimization at the start can lead to yak shaving. Get it working first, refactor later.

Totally. Another popular acronym is YAGNI: You ain't gonna need it. It helps me to think to about that as a counterpoint to other good programming principles.

Collapse
ha404 profile image
Chris Ha

When using styled-components I would write those WET components you hint are unnecessary as separate components within the same file. Would that still count?

const SmallBlueLabel = styled.small`
  color: blue;
`

const HeroHeader = type => styled.withComponent(type)`
  font-weight: 700;
`

export default = () => (
  <div>
    <HeroHeader type="h1">...</HeroHeader>
    <SmallBlueLabel>...</SmallBlueLabel>
  </div>
)
Collapse
dwmiller profile image
David Miller

I'd say it's premature, but hardly the end of the world. If you're all in on styled components, it makes sense as you're also replacing your need for a stylesheet.

That said, for the love of god don't export the things. I just inherited a project where the original dev was doing what your example is doing, but in addition he was exporting these tiny components and using them elsewhere. So now everything is weirdly intermingled with these trivial dependencies and it's a nightmare to trace around the application

Collapse
ha404 profile image
Chris Ha

Yeah, styled-components adopts the BEM philosophy of not being shared so I would never need to export them.

If I ever had to export them, they'd be in their own file contained in a shared component folder.

Collapse
mnivoliez profile image
mnivoliez

What a useful example, I shall use it for future reference (I'll quote you :]). I shall also extends it to sometimes making things dry can lead to even dry the meaning out of the stuff.
Here is a good video of @mpj about that:
youtube.com/embed/Bks59AaHe1c

Again, I think that the division of code should be on purpose and do not steal away the comprehension of it.

Collapse
eljayadobe profile image
Eljay-Adobe

I've heard WET as "Write Expressive Tests".

In the context of unit tests (TDD-style), it is better to have independent unit tests that are not dependent on other unit tests or convenience functions (such as set-up or tear-down). So, indeed, these kinds of unit tests violate DRY because they embrace WET.

As a consequence, WET ("Write Expressive Tests") does mean WET ("We Enjoy Typing").

Collapse
chrisvasqm profile image
Christian Vasquez

Cheers Serena!

<3'ed the way you explained each example :)

Collapse
phlash909 profile image
Phil Ashby

Very nice reminder that DRY is a grander idea than avoiding code duplication, thanks :)

Collapse
taverasmisael profile image
Misael Taveras

I found myself extra guilty of being a wet enthusiast. Thanks for the post, and sweet Tracer references!

Collapse
joshcheek profile image
Josh Cheek

Completely agree.