Squid Game: The Clean Code Trials


“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 these lambdas and enums instead of just if-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.”


Share this content:

I am a passionate blogger with extensive experience in web design. As a seasoned YouTube SEO expert, I have helped numerous creators optimize their content for maximum visibility.

Leave a Comment