Skip to content

DRY vs WET Code: The Art of Magical Code Refinement ✨

Welcome to another fascinating lesson at Hogwarts School of JavaScript Wizardry! Today, we'll explore two contrasting principles in the magical world of coding: DRY (Don't Repeat Yourself) and WET (Write Everything Twice/We Enjoy Typing) 🧙‍♂️

Introduction 📜

Professor McGonagall's Wisdom

"Just as we don't cast the same spell repeatedly when one well-cast spell would suffice, we shouldn't write the same code over and over when we can write it once and reuse it!"

Key Concepts:

  • DRY: Don't Repeat Yourself 🌟
  • WET: Write Everything Twice/We Enjoy Typing 💧
  • Finding the right balance between the two 🎭

Basic Concepts with Examples 🎯

What is DRY Code? 🌟

DRY (Don't Repeat Yourself) is like having a spell that you can cast multiple times without rewriting it in your spellbook.

javascript
// ❌ WET Code (Repetitive)
function calculateGryffindorPoints(score) {
    if (score > 90) return "Outstanding";
    if (score > 80) return "Exceeds Expectations";
    if (score > 70) return "Acceptable";
    return "Needs Improvement";
}

function calculateSlytherinPoints(score) {
    if (score > 90) return "Outstanding";
    if (score > 80) return "Exceeds Expectations";
    if (score > 70) return "Acceptable";
    return "Needs Improvement";
}

// ✨ DRY Code (Reusable)
function calculateHousePoints(score) {
    if (score > 90) return "Outstanding";
    if (score > 80) return "Exceeds Expectations";
    if (score > 70) return "Acceptable";
    return "Needs Improvement";
}

What is WET Code? 💧

WET code (Write Everything Twice/We Enjoy Typing) is like writing the same spell multiple times in your spellbook.

javascript
// Example of WET Code
const spellDamage1 = basePower * multiplier + bonusPower;
const spellDamage2 = basePower * multiplier + bonusPower;
const spellDamage3 = basePower * multiplier + bonusPower;

// DRY Alternative
function calculateSpellDamage(basePower, multiplier, bonusPower) {
    return basePower * multiplier + bonusPower;
}

Common Pitfalls ⚠️

Pitfall

Beware of these magical mishaps!

javascript
// ❌ Over-DRY (Too Abstract)
class MagicalEntityFactoryBuilderSingleton {
    // Too generic and complex!
    createEntityFactoryBuilder(type, config, options, metadata) {
        // Overly abstracted code that's hard to understand
    }
}

// ❌ Too WET (Too Specific)
function calculateGryffindorFirstYearPoints() { /* ... */ }
function calculateGryffindorSecondYearPoints() { /* ... */ }
function calculateGryffindorThirdYearPoints() { /* ... */ }
// ... and so on for each year and house

Do's and Don'ts 🚫✅

Do's ✅

javascript
// ✅ DRY when logic is truly identical
class Spell {
    cast(target) {
        if (!this.canCast()) {
            return this.handleCastFailure();
        }
        return this.performCast(target);
    }
}

// ✅ Create reusable utilities
const SpellUtils = {
    calculateDamage(base, multiplier) {
        return base * multiplier;
    },
    checkManaRequirement(cost, available) {
        return available >= cost;
    }
};

// ✅ Use parameters for variations
function awardPoints(house, points, reason) {
    return `${points} points to ${house} for ${reason}!`;
}

Don'ts 🚫

Don'ts

javascript
// 🚫 Don't DRY code that might need to vary
class Spell {
    // Different spells might need different casting logic
    static genericCastLogic(spellType) {
        // This might be too rigid
    }
}

// 🚫 Don't repeat complex logic
function processGryffindorStudent(student) {
    // Complex logic duplicated for each house
}
function processSlytherinStudent(student) {
    // Same complex logic duplicated
}

// 🚫 Don't create overly generic abstractions
class MagicalEntityProcessor {
    // Too abstract to be useful
    process(entity, type, config, options) {
        // Overly complex generic logic
    }
}

Before & After Examples 🔄

Before (WET Code)

javascript
class Gryffindor {
    awardPoints(points) {
        this.points += points;
        if (this.points > 100) {
            console.log("Gryffindor is leading!");
        }
        return this.points;
    }
}

class Slytherin {
    awardPoints(points) {
        this.points += points;
        if (this.points > 100) {
            console.log("Slytherin is leading!");
        }
        return this.points;
    }
}

After (DRY Code)

javascript
class House {
    constructor(name) {
        this.name = name;
        this.points = 0;
    }

    awardPoints(points) {
        this.points += points;
        if (this.points > 100) {
            console.log(`${this.name} is leading!`);
        }
        return this.points;
    }
}

const gryffindor = new House("Gryffindor");
const slytherin = new House("Slytherin");

Practical Tasks 📚

Task 1: Refactoring WET Code

Task

Refactor this WET code into DRY code:

javascript
function checkGryffindorWandPermission(student) {
    if (student.year < 1) return false;
    if (student.hasDisciplinaryAction) return false;
    if (!student.hasParentalConsent) return false;
    return true;
}

function checkSlytherinWandPermission(student) {
    if (student.year < 1) return false;
    if (student.hasDisciplinaryAction) return false;
    if (!student.hasParentalConsent) return false;
    return true;
}

function checkRavenclawWandPermission(student) {
    if (student.year < 1) return false;
    if (student.hasDisciplinaryAction) return false;
    if (!student.hasParentalConsent) return false;
    return true;
}
Answer
javascript
function checkWandPermission(student) {
    return student.year >= 1 
           && !student.hasDisciplinaryAction 
           && student.hasParentalConsent;
}

// Usage
const gryffindorStudent = { year: 1, hasDisciplinaryAction: false, hasParentalConsent: true };
const canUseWand = checkWandPermission(gryffindorStudent);

Task 2: Finding the Balance

Task

Refactor this code to find the right balance between DRY and WET:

javascript
// Too WET
class Potion {
    brewHealthPotion() {
        console.log("Adding herbs");
        console.log("Stirring clockwise");
        console.log("Heating to 100 degrees");
        return "Health Potion";
    }

    brewManaPotion() {
        console.log("Adding herbs");
        console.log("Stirring clockwise");
        console.log("Heating to 100 degrees");
        return "Mana Potion";
    }

    brewStrengthPotion() {
        console.log("Adding herbs");
        console.log("Stirring clockwise");
        console.log("Heating to 100 degrees");
        return "Strength Potion";
    }
}
Answer
javascript
class Potion {
    constructor() {
        this.potionTypes = {
            health: { color: "red", power: 50 },
            mana: { color: "blue", power: 30 },
            strength: { color: "green", power: 40 }
        };
    }

    brewPotion(type) {
        if (!this.potionTypes[type]) {
            throw new Error(`Unknown potion type: ${type}`);
        }

        this.addIngredients(type);
        this.stir();
        this.heat();

        return {
            type,
            ...this.potionTypes[type],
            name: `${type.charAt(0).toUpperCase() + type.slice(1)} Potion`
        };
    }

    addIngredients(type) {
        console.log(`Adding ${type} ingredients`);
    }

    stir() {
        console.log("Stirring clockwise");
    }

    heat() {
        console.log("Heating to 100 degrees");
    }
}

// Usage
const potionMaster = new Potion();
const healthPotion = potionMaster.brewPotion("health");

Real-World Applications 🌍

  1. Form Validation
javascript
// DRY Form Validation
class FormValidator {
    static rules = {
        required: value => !!value || "This field is required",
        email: value => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || "Invalid email",
        minLength: (value, min) => value.length >= min || `Min length is ${min}`
    };

    static validate(value, rules) {
        return rules.reduce((errors, rule) => {
            const error = this.rules[rule.type](value, rule.param);
            return error === true ? errors : [...errors, error];
        }, []);
    }
}
  1. API Error Handling 🔮
javascript
// DRY Error Handler
class APIErrorHandler {
    static handleError(error) {
        const errorMap = {
            400: "Invalid request",
            401: "Unauthorized",
            404: "Resource not found",
            500: "Server error"
        };

        return {
            code: error.status,
            message: errorMap[error.status] || "Unknown error",
            details: error.data
        };
    }
}

Additional Study Materials 📖

References 📚

  1. DRY Principle - Wikipedia
  2. The Pragmatic Programmer
  3. Clean Code

Conclusion 🎉

Remember, young wizards, DRY and WET are not about strictly following rules, but about finding the right balance:

  • DRY when the logic is truly identical 📝
  • WET when variations are needed or abstraction adds complexity ⚡
  • Use your judgment to find the sweet spot 🎯
  • Code maintainability is the ultimate goal ✨

Dumbledore's Final Words

"The difference between DRY and WET code is much like the difference between a well-cast spell and multiple attempts at the same charm. Choose wisely, but remember - sometimes a simple repetition is clearer than a complex abstraction!" 🧙‍♂️