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.
// ❌ 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.
// 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!
// ❌ 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 ✅
// ✅ 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
// 🚫 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)
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)
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:
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
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:
// 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
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 🌍
- Form Validation ✨
// 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];
}, []);
}
}
- API Error Handling 🔮
// 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 📚
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!" 🧙♂️