This article is part of writing clean code and writing unbreakable software series wherein we will understand alternate ways to present our code, which makes it more readable and maintainable.
NOTE: There is nothing wrong with if/else and switch block (when you start coding). But as system gets complex the tree of if/else, switch statement will cause maintenance nightmare.
And for trivial or one of or proof of concept applications feel free to use if/else or switch statements.
We will understand the problem with if/else and (you can replace if/else with switch statement).
The Problem
To simulate we will create a basic user interface with some buttons. And on click of the button, respective actions will be invoked.
Let’s take a look at the HTML first.
<button onclick="onButtonClick('guest',1)" id="btnGuest">Guest </button>
<button onclick="onButtonClick('admin',1)" id="btnAdmin">Admin </button>
<button onclick="onButtonClick('admin',4)" id="btnAdmin4">Admin 4 </button>
From the above html we can take a guess that every action has its corresponding logic defined, in this case, in the onButtonClick handler.
NOTE: I have kept the names, quite simple, as the core of the discussion here is replacing if/else, switch statements.
Now, let’s take a look at the typical way most developers approach the problem (including me). The logic that we want to implement is that based on the role and status we have to execute specific piece of code.
The actual logic doesn’t matter but I hope you got the context.
So, you will see below a visual hierarchy is getting created.
const onButtonClick = (role,status)=>{
alert(role + status);
if(role === 'guest'){
if(status === 1){
//do this
}else if(status === 2){
//do this
}else if(status === 3){
//do this
}else if(status === 4){
//do this
}else if(status === 5){
//do this
}else {
//do this
}
}else if(role === 'admin') {
if(status === 1){
//do this
}else if(status === 2){
//do this
}else if(status === 3){
//do this
}else if(status === 4){
//do this
}else if(status === 5){
//do this
}else {
//do this
}
}
}
Now, you could imaging where this could lead to if unattended. The if/else will keep on growing with new changes in the system.
Now, instead of if/else, some people would use switch statement. But the problem still remains the same.
So, let’s take a look at how we could optimize the above block of code and make it a bit more readable and maintainable.
Better version of above code
The event handler remains the same as shown below.
const onButtonClick = (role,status)=>{
let action = getActionMethod(role, status);
action(); // You can also pass parameter
}
// The getActionMethod is just a helper
const getActionMethod = (role, status) => {
let key = `${role}_${status}`;
let action = actions[key] || actions["default"];
return action;
}
In the above method the getAction method just gets the right method to execute based on a key/value or hash table.
Take a look at the code below.
const actions ={
'guest_1': (...args)=>{console.log("Guest 1: ", args)},
'guest_2': ()=>{console.log("Guest 2")},
'guest_3': ()=>{console.log("Guest 3")},
'guest_4': ()=>{console.log("Guest 4")},
'guest_5': ()=>{console.log("Guest 5")},
'admin_1': ()=>{console.log("Admin 1")},
'admin_2': ()=>{console.log("Admin 2")},
'admin_3': ()=>{console.log("Admin 3")},
'admin_4': ()=>{console.log("Admin 4")},
'admin_5': ()=>{console.log("Admin 5")},
'default': ()=>{console.log("Default")},
}
đź’ˇ In the above code I have in-lined the methods, but you can put each logic into a separate function, or file, or module as needed.
Share your thoughts in the comments (I will update the final solution later) as reading without practice won’t lead to effective learning.
In fact use the same concept in your favorite language and post solutions (so collective learning will make us even more better).
đź’Ş Anyone up for the challenge! Make the code better and more dynamic so that we can reach towards the goal of “Writing Unbreakable Software”.
Also please note, I have shown only one scenario. For other scenarios and combinations the code has to be modified accordingly.
NOTE: The above is the representative version of the code. You can even make it more better. If your logic are more complex, those can be in separate file, module and loaded dynamically.
Additional Reading
- Read more about strategy design pattern
- Read more about Command Pattern
- Read more about Factory (object construction patterns)
- Read more about Command Pattern
Switch may not be the replacement for if else all time. What if we wanted to compare different scenarios and do accordingly? Here our switch cases always check the status. What if we wanted to compare different scenarios on each case?
Yes. It’s where switch and if/else are used interchangeably.
Otherwise, your primary replacement criteria is for if/else ladder.
So, in either case if you have if/else or switch(it may not be mutually inclusive or exclusive, depending on what you use) as applicable you can always put a better design.
In the above case I have shown code where they are used interchangeably but in all cases alternative approach will fit. But ofcourse you have to modify the code a accordingly.