Switch is EVIL! (Part 3)

In the last two posts we got rid of switch cases by either converting them to array (simple value-replacement) or maps (disjoint value-replacements). But life is not always so simple, no? What if we have piece of code inside each case statement? Surely that can’t be stuffed in an array (Actually, It can be in a JavaScript array). *Note: I am using JavaScript but trying to keep code such that it can be implemented in any language*

Let's look at this beast of a code

This JavaScript program offers "case conversion" 
of a string or an array of strings. 

changeCase("IoT", "lower"); // iot
changeCase(["MY", "VAR], "camel"); // myVar

How it does is very pathetic - 
An if-else-if ladder, which will quickly 
grow huge with more conversions being added, 
not to mention the bugs that will be created 
when its modified and regression testing required after each modification.
/**
* A function with an if-else-if ladder
* to determine which case conversion is to be done
*/
function changeCase(caseType,words) {
// We support lower and camelCase
// e.g changeCase("lower", "IoT")
// or changeCase("camel", "MY", "VAR")
// Sanitize user input to "lower", "camel" etc.
caseType = caseType.trim().toLowerCase();
// Get ready to convert
let result = "";
// Lower case
if (caseType == "lower") {
// Concatenate each element as lower
for (word of words) {
result += word
.toString()
.trim()
.toLowerCase();
}
} else if (caseType == "camel") {
// first word is lower
result = words[0]
.toString()
.trim()
.toLowerCase();
// Now rest of the words are Proper case
for (let offset = 1;
offset < words.length;
offset++) {
// First letter upper
result += words[offset]
.slice(0, 1)
.toUpperCase();
// Rest all lower
result += words[offset]
.slice(1)
.toLowerCase();
}
}
return result;
}
view raw ifelseladder.js hosted with ❤ by GitHub

What an ugly piece of code (even though it seems well formatted and commented). We have first cousin of switch-case here … if-else-if ladder. Just like switch-case, if-else ladders tend to expand and add more else if’s to existing code. What makes it worse is that it’s not some value replacement which can be understood easily (case 1: result=”Mon”).

Where there is a switch, there WILL be more cases and where there is an else-if, there WILL be more else-if

Sanjay Vyas

What makes such “code” inside cases more worrisome is that during the maintenance phase, new set of developers will modify this code. The new developers are probably junior devs who do not have the awareness of this code. They are like Intern Doctors asked to do surgery on a patient. Guess what will happen most of the time? They will look at the existing code, try to figure out what is being done in other cases and BANG! copy-paste the existing code and then try to change it. That makes it worse

Copy-paste is the fastest way of creating Bugs.

Second fastest way is switch-case or if-else ladder

Sanjay Vyas
Taming the beast

Just like the disjoint set of values for case statement, where we used a map (JavaScript object), we can use a map here between the caseType and actual code. First we need to eliminate the individual if-else statements and separate the case converter code into separate functions.

/**
* Separate functions for each case conversion we support
* lower
* camel
* … more will be added in future
*/
function convertToLower(words)
{
let result = "";
// Concatenate each element as lower
for (word of words) {
result += word
.toString()
.trim()
.toLowerCase();
}
return result;
}
function convertToCamel(words) {
let result = "";
// first word is lower
result = words[0]
.toString()
.trim()
.toLowerCase();
// Now rest of the words are Proper case
for (let offset = 1;
offset < words.length;
offset++) {
// First letter upper
result += words[offset]
.slice(0, 1)
.toUpperCase();
// Rest all lower
result += words[offset]
.slice(1)
.toLowerCase();
}
return result;
}
view raw Convertors.js hosted with ❤ by GitHub
Now let's write the changeCase again
/**
* Our main function has simplied a lot
*/
function changeCase(word, caseType) {
// We create a map of caseType -> function
const caseMap = {
"lower": convertToLower,
"camel": convertToCamel
}
// Look up the caseType "lower", "camel"
// The map will return its function, call it
caseType = caseType.trim().toLowerCase();
var conversionFunction = caseMap[caseType];
// Do we have this conversion function?
if ( conversionFunction != null )
return conversionFunction(word);
else
return null;
}
view raw ChangeCase.js hosted with ❤ by GitHub

Wow! This looks so cool and maintainable. If we have another case conversion types, all we have to do is write that function and “plug” it into the map. So let’s do it.

Adding a new case converter 'Proper'
/**
* Adding a new case converter called Proper case
* Proper case is also called Pascal case
* Where every word begins with an upper case
* All the remaiing letters of the words are lower
*/
function convertToProper(words) {
// Proper case is also called PascalCase
// Every word starts with an upper letter
// All the remaining letters are lower
let result = "";
// Go thru every word
for (word of words) {
// First letter is upper
result += word
.slice(0, 1)
.toUpperCase();
// Rest all are lower
result += word
.slice(1)
.toLowerCase();
}
}
return result;
}
view raw ProperCase.js hosted with ❤ by GitHub

We are almost done. We have added a NEW function and not tried to modify existing code in a huge switch-case/if-else-if. In fact, the new set of developers don’t even have to look at the existing UGLY code. True, they can still create bugs in their code but guess what… we won’t have to do regression testing on our existing code. If there are bugs found in testing, they can ONLY be in the new code that the new devs have written. Well, they are kids… we can go easy of them and help them. But at least they are NOT creating chaos in our existing code.

So, what happens to our main function now? It’s so easy.. all we have to do is update our map with this new functionality.

Adding new functionality is a breeze now
**
* Adding new functionality does not require modify "code"
* We can simply update our map with new case converter
*/
function changeCase(word, caseType) {
const caseMap = {
"lower": convertToLower,
"camel": convertToCamel,
"proper": convertToProper
}
// Convert switch case to map(string, function)
caseType = caseType.trim().toLowerCase();
var conversion = caseMap[caseType];
// Make sure we got the conversion function
return conversion? caseMap[caseType](word) : null;
}
view raw UpdateMap.js hosted with ❤ by GitHub

Hey! That was easy, right? Once we understand what causes frequent modification to our code (duh! switch-case and if-else), we can refactor our application to become array or map driven. Regression testing is passe and new devs cannot create chaos in existing code. Life becomes so simple, no?

You may ask.. all this is easy because its JavaScript. How about languages like Java, C# or C++ (brr!). How do we implement these there? Well! The concept remains the same but the language may offer varying support. For example, in other languages, we can create map of string and function pointer.

In C++ we can create
map<string, string (*converter)(list<string> word)>

In C# we can create
Dictionary<string, Func<string, string>>(params string word);

In Java we can create
HashMap<string, ConverterInterface>(String[] word);

So the point is… In ALL languages, the switch MUST DIE!

Well, this is not the end of it. I don’t like the idea of even modifying the changeCase function to insert “proper” into the map. Even THAT is “modifying” the code.

True maintainable code should be when we can add new case converters WITHOUT even looking as changeCase or other conversion functions. You think you can do that? Let me know!

Stay tuned for more Switchzilla (Switch Killer) 👍

Switch is EVIL series

  1. Switch is EVIL! (Part 1) – Convert it to arrays
  2. Switch is EVIL! (Part 2) – Convert it to maps
  3. Switch is EVIL! (Part 3) – Create function maps
  4. Switch is EVIL! (Part 3a) – Implementing in Java
  5. Switch is EVIL! (Part 3b) – Implementing in C#

2 thoughts on “Switch is EVIL! (Part 3)

  1. For the convertTo*(word) functions, why have the “if” there? Why not always pass an array, be it a single value or many values? Avoid the “if-else” as well.

    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s