“In the world of code, there are only two outcomes: evolve or perish.”
Episode 1: The Red Light of Rigid Code
Scene: A vast ODC filled with desks, whiteboards, and terminals. The chairs are marked with red and green stickers. Software contestants sitting, trembling, gazing at a giant robot doll that watches them intently.
A loudspeaker booms:
“Your first challenge: implement a discount calculator. It must work today… and still work tomorrow.”
Contestant #101, a confident junior developer, leans on the terminal and types:
public class DiscountCalculator {
public double calculate(String customerType, double amount, String couponCode, int orderCount, LocalDateTime firstOrderDate) {
double discount = 0;
if ("REGULAR".equals(customerType)) {
discount = amount * 0.1;
if ("FEST10".equalsIgnoreCase(couponCode)) {
discount += 10;
}
} else if ("PREMIUM".equals(customerType)) {
discount = amount * 0.15;
if ("FEST10".equalsIgnoreCase(couponCode)) {
discount += 20;
}
} else if ("VIP".equals(customerType)) {
discount = amount * 0.25;
if (orderCount > 5 && firstOrderDate.isBefore(LocalDateTime.now().minusYears(2))) {
discount += 50;
}
}
if (discount > 100) {
discount = 100; // cap
}
System.out.println("Discount: " + discount);
return discount;
}
}
The doll’s eyes turn red. A siren blares.
Boom. Eliminated.
Why This Code Died: Exposing Anti-Patterns
1. God Class / God Method
One class or method tries to handle all responsibilities. That’s a sign it’s doing too much and is hard to maintain.
Scene: Flashback — Contestant 101 in the waiting area.
Developer Mentor: “You stacked all logic in one method. You became the God… and the God fell.”
- Mixes validation, business logic, coupon logic, and logging.
- Hard to test specific paths in isolation.
- Not KISS (Keep It Simple & Stupid).
2. Magic Strings and Constants
Scene: Flashback — A string typo breaks production.
Developer Intern: “I typed ‘PremIum’ instead of ‘PREMIUM’… 11,000 customers got no discount.”
- Strings like
"REGULAR"
,"FEST10"
,"VIP"
are error-prone and scattered. - No compile-time safety, hard to change later.
3. If-Else Tower (No Polymorphism)
Scene: Front Man shows an old dusty code file with 43 else-ifs.
Front Man: “The longer the tower, the harder it is to escape.”
- Adding a new type (e.g., “WHOLESALE”) means editing the method — violates OCP (Open/Close Principal.)
- Code becomes unreadable.
4. Tight Coupling
- Logic is tied to exact string values.
- System.out in business logic — violates the separation of concerns.
5. Not Testable
- How will you unit test VIP logic alone?
- Everything is entangled.
6. No Reusability or Extensibility
Refactor: Code that Survives the Next Round
Scene: Developer’s Resurrection
Mentor: “You’re not out. Refactor. And survive.”
Step 1: Create Enum for CustomerType
public enum CustomerType {
REGULAR, PREMIUM, VIP;
public static CustomerType from(String value) {
try {
return CustomerType.valueOf(value.toUpperCase());
} catch (Exception e) {
throw new IllegalArgumentException("Invalid customer type: " + value);
}
}
}
Step 2: Use Strategy Pattern
public interface DiscountStrategy {
CustomerType type();
double calculate(double amount, String couponCode, int orderCount, LocalDateTime firstOrderDate);
}
Implement a few strategies:
public class RegularDiscount implements DiscountStrategy {
public CustomerType type() { return CustomerType.REGULAR; }
public double calculate(double amount, String couponCode, int orderCount, LocalDateTime firstOrderDate) {
double discount = amount * 0.1;
if ("FEST10".equalsIgnoreCase(couponCode)) discount += 10;
return discount;
}
}
public class PremiumDiscount implements DiscountStrategy {
public CustomerType type() { return CustomerType.PREMIUM; }
public double calculate(double amount, String couponCode, int orderCount, LocalDateTime firstOrderDate) {
double discount = amount * 0.15;
if ("FEST10".equalsIgnoreCase(couponCode)) discount += 20;
return discount;
}
}
public class VipDiscount implements DiscountStrategy {
public CustomerType type() { return CustomerType.VIP; }
public double calculate(double amount, String couponCode, int orderCount, LocalDateTime firstOrderDate) {
double discount = amount * 0.25;
if (orderCount > 5 && firstOrderDate.isBefore(LocalDateTime.now().minusYears(2))) {
discount += 50;
}
return discount;
}
}
Step 3: Make It Generic and Plug With Lambdas
public class StrategyRegistry {
private final Map> strategies = new HashMap<>();
public void register(T key, Function strategyFunc) {
strategies.put(key, strategyFunc);
}
public double apply(T key, R request) {
return strategies.getOrDefault(key, r -> 0.0).apply(request);
}
}
Scene: The Intern’s Orientation
(A new intern enters the Squid Game Arena’s code lab, shaking with nervousness. Developer Mentor 218 greets him with a calm voice.)
Intern:
“Sir… what is this
StrategyRegistry
thing? And… why use theselambdas
and enums instead of justif-else
?”
Mentor 218 (sips tea and opens laptop):
“Let me show you. This is how we survive chaos.”
What Is StrategyRegistry?
It’s a generic plug-and-play engine that avoids the “god method” syndrome. You pass:
T
: A key — in our case,CustomerType
.R
: A request payload — here,DiscountRequest
.
Each strategy is a function:
And we store them in a map:
Instead of giant if-else
logic like:
if(customerType.equals("PREMIUM")) { ... }
We now just do:
registry.apply(customerTypeEnum, request);
Registering Discount Strategies Using Lambdas
StrategyRegistry registry = new StrategyRegistry<>();
registry.register(CustomerType.REGULAR, req -> {
double discount = req.amount() * 0.1;
if ("FEST10".equalsIgnoreCase(req.couponCode())) discount += 10;
return discount;
});
registry.register(CustomerType.PREMIUM, req -> {
double discount = req.amount() * 0.15;
if ("FEST10".equalsIgnoreCase(req.couponCode())) discount += 20;
return discount;
});
registry.register(CustomerType.VIP, req -> {
double discount = req.amount() * 0.25;
if (req.orderCount() > 5 && req.firstOrderDate().isBefore(LocalDateTime.now().minusYears(2))) {
discount += 50;
}
return discount;
});
Why Use Functional Programming?
1. No New Classes for Every Variation
- Avoids boilerplate like
PremiumDiscountStrategy implements DiscountStrategy
- Lambdas are lightweight and in-place
2. Easier to Test
- Each lambda can be tested in isolation.
- You can inject different behaviors in tests.
3. Open for Extension, Closed for Modification (OCP)
- Add a new type without touching existing logic.
- Register strategy on startup, config, or runtime.
4. Reusability and Modularity
- Your registry becomes a plugboard. Great for rule engines!
Magic Strings: Death by Literal Comparison
The original code had:
if ("FEST10".equalsIgnoreCase(couponCode)) ...
Instead of "FEST10"
scattered everywhere:
1. Create a property-based config:
discount.coupon.festive = FEST10
2. Bind it to a configuration class: If you are using a Spring Boot-like framework.
@ConfigurationProperties(prefix = "discount.coupon")
public class DiscountCouponProperties {
private String festive;
public String getFestive() {
return festive;
}
public void setFestive(String festive) {
this.festive = festive;
}
}
3. Inject it where you define strategy:
String fest10 = couponProperties.getFestive();
registry.register(CustomerType.REGULAR, req -> {
double discount = req.amount() * 0.1;
if (fest10.equalsIgnoreCase(req.couponCode())) discount += 10;
return discount;
});
Updated Registry Registration (More Generic and Clean)
@Autowired
DiscountCouponProperties couponProperties;
...
registry.register(CustomerType.VIP, req -> {
double discount = req.amount() * 0.25;
boolean loyal = req.orderCount() > 5 &&
req.firstOrderDate().isBefore(LocalDateTime.now().minusYears(2));
if (loyal) discount += 50;
return discount;
});
Refactored Strategy class:
public class DiscountStrategyRegistration {
public static StrategyRegistry createRegistry() {
StrategyRegistry registry = new StrategyRegistry<>();
@Autowired
DiscountCouponProperties couponProperties;
registry.register(CustomerType.REGULAR, req -> {
double discount = req.amount() * 0.1;
if (couponProperties.getFestive().equalsIgnoreCase(req.couponCode())) discount += 10;
return discount;
});
registry.register(CustomerType.PREMIUM, req -> {
double discount = req.amount() * 0.15;
if (couponProperties.getFestive().equalsIgnoreCase(req.couponCode())) discount += 20;
return discount;
});
registry.register(CustomerType.VIP, req -> {
double discount = req.amount() * 0.25;
if (req.orderCount() > 5 && req.firstOrderDate().isBefore(LocalDateTime.now().minusYears(2))) {
discount += 50;
}
return discount;
});
return registry;
}
}
Final Refactored Calculator
package com.example.javaonfly.antipattern.discount;
import java.time.LocalDateTime;
public class DiscountCalculator {
private final StrategyRegistry registry;
public DiscountCalculator(StrategyRegistry registry) {
this.registry = registry;
}
public double calculate(String customerTypeStr, double amount, String couponCode, int orderCount, LocalDateTime firstOrderDate) {
CustomerType type = CustomerType.from(customerTypeStr);
DiscountRequest req = new DiscountRequest(amount, couponCode, orderCount, firstOrderDate);
return Math.min(100, registry.apply(type, req));
}
public static void main(String[] args) {
DiscountCalculator calculator = new DiscountCalculator(DiscountStrategyRegistration.createRegistry());
double value = calculator.calculate("REGULAR",100,"FEST10",1,null);
System.out.println("Discount calculated :: " + value);
}
}
//output
Discount calculated :: 20.0
Scene Epilogue
Contestant 456: “So… you don’t write code for today?”
Front Man: “No. We write for tomorrow. For change. For chaos. And we survive.”
Contestant 212: “What if change never comes?”
Front Man: “Then your clean code will never break. Isn’t that peace?”
Clean Code Checklist From Episode 1
Anti-Pattern | Death Cause | Cure |
---|---|---|
God Class | All logic in one | SRP: Break into strategies |
Magic Strings | Typos & unclear | Enums and constants |
SRP Violation | One class, many jobs | Delegate to strategy classes |
OCP Violation | Editing old code | Plug strategy via registry |
Unreadable Code | Future devs hate it | Descriptive interfaces |
Hard to Test | No unit slices | Modular + Generic strategies |
“Survive by writing clean code that’s ready for change — not written in fear of it.”