DRY - Don't Repeat Yourself
The Principle
Every piece of knowledge must have a single, unambiguous representation within a system. Duplication leads to inconsistency when only one copy is updated.
Good DRY: Extracting Price Calculation
// BAD: Duplicated in multiple places
// In DiscountCalculator
$finalPrice = $product->getPrice() * (1 - $discount / 100);
$finalPrice = $finalPrice * (1 + $taxRate);
// In OrderTotal
$finalPrice = $item->getPrice() * (1 - $item->getDiscount() / 100);
$finalPrice = $finalPrice * (1 + $item->getTaxRate());
// GOOD: Single source of truth
namespace Vendor\Catalog\Model\Pricing;
class PriceCalculator
{
public function calculateFinalPrice(float $price, float $discount, float $taxRate): float
{
$discounted = $price * (1 - $discount / 100);
return $discounted * (1 + $taxRate);
}
}
// Both classes use PriceCalculator via dependency injection
When DRY Becomes Harmful
// BAD: Forced DRY couples unrelated things
namespace Vendor\Catalog\Model;
class Product implements \Vendor\Sales\Api\HasPriceInterface
{
// Product price calculation
public function calculatePrice(float $base, float $tax): float
{
return $base * (1 + $tax);
}
}
// Later, someone reuses this for shipping...
class ShippingCalculator implements \Vendor\Sales\Api\HasPriceInterface
{
// Shipping uses same method but tax logic differs!
public function calculatePrice(float $base, float $tax): float
{
return $base * (1 + $tax); // Wrong! Shipping tax is different
}
}
This is called "accidental duplication" - the code looks similar but the requirements diverge. Sharing the implementation creates a hidden coupling.
Rule of Thumb
DRY applies to knowledge, not just code. If two things change for the same reason, they should be unified. If they change for different reasons, keep them separate even if they look identical now.
KISS - Keep It Simple, Stupid
The Principle
Most systems work best if they are kept simple rather than made complicated. Simplicity should be a key goal in design.
Violation: Over-Engineered Validation
namespace Vendor\Checkout\Validator;
interface ValidationStrategyInterface
{
public function validate(mixed $value): ValidationResultInterface;
}
namespace Vendor\Checkout\Validator\Strategy;
class StringNotEmptyStrategy implements ValidationStrategyInterface
{
public function __construct(
private ValidationContextFactory $contextFactory,
private ValidationResultBuilder $resultBuilder
) {}
public function validate(mixed $value): ValidationResultInterface
{
$context = $this->contextFactory->create();
return $this->resultBuilder
->setContext($context)
->addRule(new NotEmptyRule())
->addRule(new StringTypeRule())
->build($value);
}
}
This is 20 lines of code to check if a string is empty.
KISS Version
namespace Vendor\Checkout\Validator;
class AddressValidator
{
public function validate(array $data): array
{
$errors = [];
if (empty(trim($data['street'] ?? ''))) {
$errors[] = 'Street address is required';
}
if (empty(trim($data['city'] ?? ''))) {
$errors[] = 'City is required';
}
if (empty($data['postcode'])) {
$errors[] = 'Postcode is required';
}
return $errors;
}
}
15 lines, immediately readable, easy to modify. The over-engineered version adds cognitive overhead without real benefit.
KISS in Magento Context
// Over-engineered: factory + strategy + builder for simple config
public function getShippingMethod(): string
{
$factory = $this->methodFactoryRegistry->get($this->config->getMethodType());
$strategy = $factory->createStrategy();
return $strategy->resolve();
}
// KISS: direct approach
public function getShippingMethod(): string
{
return $this->scopeConfig->getValue('shipping/method');
}
If it's just reading config, don't build a framework around it.
YAGNI - You Aren't Gonna Need It
The Principle
Don't implement something until it is actually needed. Every unused feature is dead code that adds maintenance burden.
Violation: Anticipatory Abstraction
namespace Vendor\Catalog\Model;
// "What if we need to support multiple file formats?"
interface ExporterInterface
{
public function export(array $data): string;
}
interface ImporterInterface
{
public function import(string $data): array;
}
interface TransformerInterface
{
public function transform(array $input): array;
}
class CsvExporter implements ExporterInterface { /* ... */ }
class JsonExporter implements ExporterInterface { /* ... */ }
class XmlExporter implements ExporterInterface { /* ... */ }
class CsvImporter implements ImporterInterface { /* ... */ }
class JsonImporter implements ImporterInterface { /* ... */ }
class XmlImporter implements ImporterInterface { /* ... */ }
// Total: 6 classes + 3 interfaces for a feature nobody asked for
YAGNI Version
namespace Vendor\Catalog\Model\Export;
// Build what you actually need right now
class ProductCsvExporter
{
public function export(): string
{
$products = $this->productCollection->load();
$csv = '';
foreach ($products as $product) {
$csv .= implode(',', [
$product->getSku(),
$product->getName(),
$product->getPrice(),
]) . "\n";
}
return $csv;
}
}
// When JSON export is actually requested:
// THEN refactor to interface + multiple implementations
The YAGNI Test
Ask yourself:
- Will we definitely use this in the next sprint? If not, don't build it.
- Is there a concrete user story? If not, it's speculation.
- Can we add it later without major refactoring? Usually yes.
When YAGNI Hurts
YAGNI doesn't mean "never plan ahead." If you're building an API and know you'll need versioning, adding a version parameter to routes from day one is wise — it's not YAGNI because you have concrete requirements for it.
The key: distinguish between uncertainty (YAGNI) and known requirements (build it now).
Quiz
1. When does DRY become harmful?
2. YAGNI advises against:
3. KISS is best demonstrated by:
Flashcards
Question
What does DRY stand for?
Click to reveal answer
Answer
Don't Repeat Yourself - single source of knowledge
Question
What does KISS stand for?
Click to reveal answer
Answer
Keep It Simple, Stupid - prefer simple solutions
Question
What does YAGNI stand for?
Click to reveal answer
Answer
You Aren't Gonna Need It - don't build until needed
Question
When is DRY harmful?
Click to reveal answer
Answer
When it couples unrelated things that change for different reasons
Revision Notes
Key Takeaways
- 1. DRY unifies knowledge that changes for the same reason
- 2. KISS prioritizes simplicity and readability over cleverness
- 3. YAGNI prevents premature feature development
- 4. All three can be harmful when applied dogmatically
- 5. Balance: apply principles pragmatically based on context
Interview Tips
- • Give examples where following these principles actually hurt a project
- • Discuss the tension between YAGNI and designing for extensibility
- • Know that Magento often errs on the side of flexibility (sometimes violating YAGNI)
Cheat Sheet
DRY → Eliminate real duplication, not apparent similarity
KISS → Simple > clever; readable > concise
YAGNI → Build it when you need it, not before
Check: Does this change for the same reason? → DRY
Check: Is this the simplest approach? → KISS
Check: Do we have a requirement for this? → YAGNI