Code Smell 321 - Getter Piggybacking

Wait 5 sec.

One broken window invites anotherTL;DR: Don't reuse an existing getter to bolt on new business logic from outside the object.Problems 😔Duplicated business rulesBroken encapsulationScattered comparison logicHidden domain knowledgeFragile refactoringLaw of Demeter violationSolutions 😃Add real behavior methodsKeep comparisons inside objectPass collaborators, not primitivesReserve getters for renderingFollow tell, don't askRefactorings ⚙️https://hackernoon.com/refactoring-027-how-to-remove-getters?embedable=truehttps://hackernoon.com/refactoring-013-eliminating-repeated-code-with-dry-principles?embedable=trueContext 💬An object exposes a getter for one legitimate reason: some other part of the system needs to read that value, usually to display it.Getters are a code smell, but this one gets a pass, for now.Later on, you discover that you need new business logic that depends on the same value.You already have the getter, so you write a function outside the object that calls it and does the comparison itself, breaking the encapsulation principle.Someone else needs slightly different logic based on the same value.They also call the getter and write their own version of the comparison.Now two places decide what that value means, and neither of them is the object that owns it. Typical.You didn't add a second getter this time.You reused the first one, because it was already there.That's the trap.The getter existed for one reason, and you let it justify skipping the real fix: a method on the object that answers the question itself, instead of handing out the raw value for every caller to interpret on their own.Don't break more windowsSample Code 💻Wrong 🚫// Food needs to show its use-by date on the shelf// label, so useByDate() exists for that one reason.//// Later, removeExpiredFood() needs to pull expired// products, so it reuses useByDate() and compares the// result to today itself, outside Food.//// flagNearExpiryFood() needs almost the same check, so// it also calls useByDate() and writes its own slightly// different comparison.//// Now two functions decide what "expired" means, and// neither of them is Food.class Food { constructor(name, useByDate) { this.name = name; this.useByDateValue = useByDate; } useByDate() { return this.useByDateValue; }}function removeExpiredFood(shelf, today) { return shelf.filter( food => food.useByDate() >= today );}function flagNearExpiryFood( shelf, today, warningDays) { return shelf.filter(food => { const daysLeft = daysBetween( food.useByDate(), today ); return daysLeft >= 0 && daysLeft !food.isExpiredOn(today));}function flagNearExpiryFood(shelf, today, warningDays) { return shelf.filter(food => { const daysLeft = food.daysUntilExpiryFrom(today); return daysLeft >= 0 && daysLeft