Goodbye Spaghetti Code: Refactoring Infinite IF-ELSE with Object Mapping
Goodbye Spaghetti Code: Refactoring Infinite IF-ELSE with Object Mapping
This phenomenon is known as Spaghetti Code, and it's the number one enemy of scalability. Today, we'll explore how to transform that logic into clean, maintainable, and professional structures using Object Literals and Maps.
1. The Problem: The "Waterfall Effect"
When you use multiple if-else blocks, the JavaScript engine evaluates each line sequentially. If the condition you're looking for is at the bottom, the program wastes resources evaluating everything above it. Furthermore, readability drops to zero.
// ❌ Hard to maintain and scale
function processNotification(type) {
if (type === 'email') {
sendEmail();
} else if (type === 'sms') {
sendSMS();
} else if (type === 'push') {
sendPush();
} else {
logError();
}
}
2. Level 1: The Lookup Pattern (Basics)
The simplest way to clean this up is to create an object where the keys are your conditions and the values are the results or functions to execute.
// ✅ Clean and direct code
const NOTIFICATIONS = {
email: sendEmail,
sms: sendSMS,
push: sendPush
};
function processNotification(type) {
// We use the || short-circuit to define a default case
const action = NOTIFICATIONS[type] || logError;
return action();
}
3. Level 2: Solving Complex Scenarios
This is where the technique becomes truly powerful for real-world applications, especially in modern Frontend development.
A. UI State Management (Perfect for React)
Instead of cluttering your component with ternary operators or if statements before the return, map your state components. This separates business logic from the view.
const UserProfileViews = {
LOADING: () => <Skeleton />,
ERROR: ({ message }) => <ErrorMessage text={message} />,
SUCCESS: ({ data }) => <UserCard user={data} />,
EMPTY: () => <EmptyState />
};
const UserProfile = ({ status, payload }) => {
// We select the component based on the server status
const Component = UserProfileViews[status] || UserProfileViews.ERROR;
return (
<div className="container">
<Component {...payload} />
</div>
);
};
B. Dynamic Range Logic
Sometimes if statements compare ranges (e.g., price > 100). For this, we use an Array of Criteria with validation functions to keep the structure organized:
const DISCOUNT_RULES = [
{ test: (val) => val > 500, apply: (val) => val * 0.80 }, // 20% OFF
{ test: (val) => val > 100, apply: (val) => val * 0.90 }, // 10% OFF
{ test: (val) => true, apply: (val) => val } // No discount
];
const calculatePrice = (amount) => {
// We search for the first matching rule declaratively
const rule = DISCOUNT_RULES.find(r => r.test(amount));
return rule.apply(amount);
};
4. Why is this approach superior?
- Constant Complexity $O(1)$: In simple key-value lookups, the speed is maximum. Regardless of whether you have 10 or 100 cases, there is no performance degradation.
- Single Responsibility Principle: You can extract configurations into external files (like
config.rules.js), keeping your main components and functions extremely lean. - Open/Closed Principle: If you need to add a new notification type or rule, you don't touch the core logic; you simply extend the configuration object or array.
Conclusion
Refactoring isn't just about "pretty code"; it's about reducing cognitive load for your "future self" and your team. Map-based code is more predictable, easier to test, and far more professional. Next time you're tempted to write that fourth else if, stop and ask yourself: "Could this be an object?"