How to Refactor Excessive if‑else Statements for Cleaner Code
Excessive if‑else statements can make code hard to maintain, but by distinguishing exception handling from state handling and applying techniques such as condition merging, early exits, removing temporary variables, and leveraging polymorphism, developers can refactor code to be clearer, shorter, and more robust.
Why do we write code full of if‑else?
Programmers often start with concise, clear code, but as requirements evolve they add many if‑else checks for null values, type validation, or different business flows, leading to large, hard‑to‑maintain functions.
In practice, if‑else appears in two main scenarios: exception‑handling logic and state‑handling logic. Exception handling has one normal path and one error path, while state handling treats all branches as normal flows.
Drawbacks of excessive if‑else
Too many if‑else statements increase logical complexity, reduce maintainability, and make bugs more likely because developers may treat all branches as equally important.
Refactoring principle
Keep the normal execution path at the outermost level. Reduce nesting, remove temporary variables, invert conditions, and merge condition expressions.
Exception‑handling refactoring example 1
<code>Object obj = getObj();
if (obj != null) {
// do something
} else {
// do something
}
</code>After merging conditions:
<code>Object obj = getObj();
if (obj != null) {
// do something
} else {
// do something
}
</code>The technique is to combine multiple conditions that lead to the same result into a single expression.
Exception‑handling refactoring example 2
<code>double disablityAmount() {
if (_seniority < 2) return 0;
if (_monthsDisabled > 12) return 0;
if (_isPartTime) return 0;
// do something
}
</code>After refactoring:
<code>double disablityAmount() {
if (_seniority < 2 || _monthsDisabled > 12 || _isPartTime)
return 0;
// do something
}
</code>This merges the three separate checks into one condition.
Exception‑handling refactoring example 3
<code>double getPayAmount() {
double result;
if (_isDead) {
result = deadAmount();
} else {
if (_isSeparated) {
result = separatedAmount();
} else {
if (_isRetired) {
result = retiredAmount();
} else {
result = normalPayAmount();
}
}
}
return result;
}
</code>After refactoring:
<code>double getPayAmount() {
if (_isDead) return deadAmount();
if (_isSeparated) return separatedAmount();
if (_isRetired) return retiredAmount();
return normalPayAmount();
}
</code>The nested if‑else is flattened, and the temporary variable is removed.
Exception‑handling refactoring example 4
<code>public ArrayList<Student> getStudents(int uid) {
ArrayList<Student> result = new ArrayList<>();
Student stu = getStudentByUid(uid);
if (stu == null) { logger.error("获取学生信息失败"); return result; }
Teacher teacher = stu.getTeacher();
if (teacher == null) { logger.error("获取老师信息失败"); return result; }
ArrayList<Student> students = teacher.getStudents();
if (students == null) { logger.error("获取学生列表失败"); return result; }
for (Student student : students) {
if (student.getAge() >= 18 && student.getGender() == MALE) {
result.add(student);
}
}
return result;
}
</code>This applies the early‑exit pattern to keep the main flow clear.
State‑handling refactoring example 1
<code>double getPayAmount() {
Object obj = getObj();
double money = 0;
if (obj.getType == 1) {
ObjectA objA = obj.getObjectA();
money = objA.getMoney() * obj.getNormalMoneryA();
} else if (obj.getType == 2) {
ObjectB objB = obj.getObjectB();
money = objB.getMoney() * obj.getNormalMoneryB() + 1000;
}
}
</code>After refactoring:
<code>double getPayAmount() {
Object obj = getObj();
if (obj.getType == 1) return getType1Money(obj);
else if (obj.getType == 2) return getType2Money(obj);
}
double getType1Money(Object obj) {
ObjectA objA = obj.getObjectA();
return objA.getMoney() * obj.getNormalMoneryA();
}
double getType2Money(Object obj) {
ObjectB objB = obj.getObjectB();
return objB.getMoney() * obj.getNormalMoneryB() + 1000;
}
</code>Encapsulating each branch into its own method shortens the if‑else block.
State‑handling refactoring example 2
Replace a switch‑style conditional with polymorphism:
<code>double getSpeed() {
switch(_type) {
case EUROPEAN: return getBaseSpeed();
case AFRICAN: return getBaseSpeed() - getLoadFactor()*_numberOfCoconuts;
case NORWEGIAN_BLUE: return (_isNailed) ? 0 : getBaseSpeed(_voltage);
}
}
</code>After refactoring:
<code>abstract class Bird { abstract double getSpeed(); }
class European extends Bird { double getSpeed() { return getBaseSpeed(); } }
class African extends Bird { double getSpeed() { return getBaseSpeed() - getLoadFactor()*_numberOfCoconuts; } }
class NorwegianBlue extends Bird { double getSpeed() { return (_isNailed) ? 0 : getBaseSpeed(_voltage); } }
</code>Using polymorphism eliminates the conditional entirely.
Summary
If‑else statements are easy to write but easy to abuse; they can quickly become tangled and hard to maintain. The key refactoring principle is to keep the normal execution path at the outermost level. Merging conditions, reducing nesting, and early‑exit of exceptional cases all help maintain a clear core flow. For state handling, either extract each branch into its own method or apply polymorphism to remove the conditional altogether.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.