We love if/else chains.
They are the first logic gates we learn. They feel safe. They feel logical. But as our systems grow, massive switch statements often signal that we are missing a critical abstraction.
We’ve all seen it: The "God Function." A single function that holds implementation details from five different, unrelated domains. It can do everything, contain anything, and as a result, it breaks every time we change anything.
Here is how to move from a brittle "Switch Statement" mess to a robust, polymorphic design.
The Junior Trap: The "Kitchen Sink" Logic
The most common place this smell appears is in notification systems. You need to send a message to a user, but you support multiple channels: Email, SMS, Slack, and Push.
The Junior developer writes a function called sendNotification. Inside, they throw every piece of logic required for every channel.
The Reality:
When you mix domains, you create risk.
- To change the Email subject line, you have to open the file that handles SMS authentication.
- If you make a typo in the Slack logic, you might syntax-error the whole file and bring down the Push notifications.
This violates the Cognitive Budget. You are forcing yourself to understand the entire notification infrastructure just to fix a typo.
The Pro Move: "Don't Let the Domains Touch"
A Professional Junior uses encapsulation. We ensure that no single class or object is aware of the logic of multiple domains.
We use Polymorphism. This sounds like a terrifying Computer Science degree word, but it just means: "Making different things look the same from the outside."
If an object looks like a Notifier and acts like a Notifier, the main system doesn't care if it sends an SMS or a carrier pigeon.
The Code: The Notification Refactor
Let's look at a function designed to handle user alerts.
// Before: The Brittle Logic
This function knows too much. It knows how to format an email subject, how to validate SMS length (160 chars), and how to talk to the Slack client.
// THE "GOD FUNCTION"
function sendNotification(user, type, message) {
switch (type) {
case 'EMAIL':
// Email specific logic mixed in here
const subject = `Message for ${user.name}`;
emailClient.send(user.email, subject, message);
break;
case 'SMS':
// SMS specific logic mixed in here
// Logic relies on 'message' being raw text
if (message.length > 160) throw Error("Too long!");
smsClient.send(user.phone, message);
break;
case 'SLACK':
// Slack specific formatting
const slackBody = { text: message, channel: user.slackId };
slackClient.post(slackBody);
break;
}
}
The Scenario: Your boss runs in. "We need to add Discord support!"
The Fear: You have to crack open this massive, tested function. You risk breaking the SMS logic just to paste in the Discord code.
// After: The Polymorphic Strategy
We move the logic into small, focused classes (strategies). The sendNotification function no longer cares how the message is sent, only that it is sent.
// 1. The logic lives in small, focused classes
class EmailNotifier {
send(user, message) {
const subject = `Message for ${user.name}`;
emailClient.send(user.email, subject, message);
}
}
class SmsNotifier {
send(user, message) {
if (message.length > 160) throw Error("Too long!");
smsClient.send(user.phone, message);
}
}
class DiscordNotifier {
send(user, message) {
discordClient.postToChannel(user.discordId, message);
}
}
// 2. The main function is now boring (and safe)
// It accepts any object that looks like a notifier
function sendNotification(notifier, user, message) {
notifier.send(user, message);
}
// Usage:
const smsStrategy = new SmsNotifier();
sendNotification(smsStrategy, currentUser, "Your order is here!");
Why This Wins
1. Zero Regression Risk
When the boss asks for "Discord Support," we don't touch sendNotification. We don't touch SmsNotifier. We just create a new file: DiscordNotifier.js.
The old code remains safe, tested, and boring.
2. Cognitive Budget
When I am working on SMS bugs, I only open SmsNotifier. I don't see the Email logic. I don't see the Slack API keys. I can focus entirely on the problem at hand without filtering out noise.
Pro Tip: Boring is beautiful. We strive for code that is so obvious it looks boring. When the solution is obvious, bugs have nowhere to hide.
Stop writing functions that know everything. Distribute the logic to the small, simple places where it belongs.
Stop writing code just to please the compiler.
This article was an excerpt from my handbook, "The Professional Junior: Writing Code that Matters."
It’s not a 400-page textbook. It’s a tactical field guide to unwritten engineering rules.
Top comments (0)