if-else statements can easily be nested and make the code unreadable if we don’t consider anything to write it. if-else is often used, so we should know how to write clean if-else statements for readability.
This post is for you if you think you often write unreadable if-else statements.
Execute short process first by the condition inversion
Let’s have a look at this example.
function if0_1(baseSalary: number, role: string, isManager: boolean): number {
let result = baseSalary;
if (isManager) {
if (role == "team leader") {
result = baseSalary * 1.2;
} else if (role == "product manager") {
result = baseSalary * 1.3;
} else if (role == "project manager") {
result = baseSalary * 1.4;
} else {
result = baseSalary * 1.1;
}
}
return result;
}
A special logic is needed only if isManager
is true. It looks not so bad at the moment because it’s small. However, the code in the if clause will be longer whenever we need to add new features.
It will be like the following and I don’t want to have such a code if possible.
if(isXxxx){
// code
// code
// code
// code
// code
// code
// code
// ... 40 lines ...
}
// do something
Additionally, nested if-else is not readable. We can improve it by the condition inversion.
function if0_2(baseSalary: number, role: string, isManager: boolean): number {
let result = baseSalary;
if (!isManager) {
return result;
}
if (role == "team leader") {
result = baseSalary * 1.2;
} else if (role == "product manager") {
result = baseSalary * 1.3;
} else if (role == "project manager") {
result = baseSalary * 1.4;
} else {
result = baseSalary * 1.1;
}
return result;
}
By condition inversion, the if-else is no longer nested and thus it’s easy to read.
De Morgan’s laws
By the way, the condition is easily inverted if you know about De Morgan’s laws. You need to reverse all the conditions.
You might get the point when you see the following.
if(isBool) <-> if(!isBool)
if(isBool && isNumber) <-> if(!isBool || !isNumber)
if(value == 5) <-> if(value != 5)
if(value > 5) <-> if(value <= 5)
Return the result early
The previous example still has room for improvement. Let’s look at the code again.
function if0_2(baseSalary: number, role: string, isManager: boolean): number {
let result = baseSalary;
if (!isManager) {
return result;
}
if (role == "team leader") {
result = baseSalary * 1.2;
} else if (role == "product manager") {
result = baseSalary * 1.3;
} else if (role == "project manager") {
result = baseSalary * 1.4;
} else {
result = baseSalary * 1.1;
}
return result;
}
A temporal variable is used in this code. Is it really needed? No. We can remove it by using return
keyword in each if clause.
function if0_3(baseSalary: number, role: string, isManager: boolean): number {
if (!isManager) {
return baseSalary;
}
if (role == "team leader") {
return baseSalary * 1.2;
} else if (role == "product manager") {
return baseSalary * 1.3;
} else if (role == "project manager") {
return baseSalary * 1.4;
}
return baseSalary * 1.1;
}
It returns the value when it’s possible. We should return as soon as possible where it’s possible to return. If return
is written only at the end of a function, we have to read the function till the end.
Furthermore, we have to check whether the used variables’ value is updated or not. The info needs to be in our head while reading the code. If we return the value, we don’t have to consider about it anymore.
Combine multiple conditions
The next example is this.
function if1_1(product: Product): number {
let result = 0;
if (product.category == "food") {
if (product.isDiscount) {
result = product.price * 0.8;
} else {
result = product.price;
}
} else if (product.category == "kitchen") {
if (product.isDiscount) {
result = product.price * 0.7;
} else {
result = product.price;
}
} else {
if (product.isDiscount) {
result = product.price * 0.9;
} else {
result = product.price;
}
}
return result;
}
As we already learned above, we can improve this. Let’s refactor it step by step. We need to carefully check whether it’s possible to apply early return. Then, apply it.
function if1_2(product: Product): number {
if (product.category == "food") {
if (product.isDiscount) {
return product.price * 0.8;
} else {
return product.price;
}
} else if (product.category == "kitchen") {
if (product.isDiscount) {
return product.price * 0.7;
} else {
return product.price;
}
} else {
if (product.isDiscount) {
return product.price * 0.9;
} else {
return product.price;
}
}
}
Okay. We could remove the temporal variable and it’s obvious that there’s only one calculation for each condition. We can remove else keyword because if clause returns a value.
function if1_3(product: Product): number {
if (product.category == "food") {
if (product.isDiscount) {
return product.price * 0.8;
}
return product.price;
} else if (product.category == "kitchen") {
if (product.isDiscount) {
return product.price * 0.7;
}
return product.price;
}
if (product.isDiscount) {
return product.price * 0.9;
}
return product.price;
}
It gets less code. We should put the multiple conditions into one to make it more readable.
function if1_4(product: Product): number {
if (product.category == "food" && product.isDiscount) {
return product.price * 0.8;
}
if (product.category == "kitchen" && product.isDiscount) {
return product.price * 0.7;
}
if (product.isDiscount) {
return product.price * 0.9;
}
return product.price;
}
If there are many conditions, it gets harder to know the meaning of the conditions. The conditions should be extracted into a function or variable in this case. Let’s define a variable with a descriptive naming.
function if1_5(product: Product): number {
const isFoodDiscount = product.category == "food" && product.isDiscount;
if (isFoodDiscount) {
return product.price * 0.8;
}
const isKitchenDiscount = product.category == "kitchen" && product.isDiscount;
if (isKitchenDiscount) {
return product.price * 0.7;
}
if (product.isDiscount) {
return product.price * 0.9;
}
return product.price;
}
The meaning of the condition is now clearer.
Overview
if-else is a basic statement that we often use during coding. It can easily get harder to understand if we don’t consider anything. Reverse the condition and return early. Then, this code can be more readable.
function if2_1(value: number): number {
let result = 0;
if (value > 10) {
if (value > 20) {
if (value <= 30) {
result = 25;
} else {
result = 99;
}
}
else {
result = 15;
}
} else {
result = 5;
}
return result;
}
It takes a while to know all the conditions in the code above but it’s much simpler with the following code.
function if2_2(value: number): number {
if (value <= 10) {
return 5;
}
if (value <= 20) {
return 15
}
if (value <= 30) {
return 25
}
return 99;
}
We can promptly understand what it does. These techniques are basic but powerful to write clean code.
Comments