Simplifying Conditionals — Tasks¶
12 hands-on exercises.
Task 1 ⭐ — Decompose Conditional (Java)¶
double charge(int quantity, Date date) {
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
return quantity * winterRate + winterServiceCharge;
}
return quantity * summerRate;
}
Solution
double charge(int quantity, Date date) {
if (notSummer(date)) return winterCharge(quantity);
return summerCharge(quantity);
}
private boolean notSummer(Date d) { return d.before(SUMMER_START) || d.after(SUMMER_END); }
private double winterCharge(int q) { return q * winterRate + winterServiceCharge; }
private double summerCharge(int q) { return q * summerRate; }
Task 2 ⭐ — Replace Nested Conditional with Guard Clauses (Java)¶
double pay(Employee e) {
double result;
if (e.isDead()) {
result = deadAmount();
} else if (e.isSeparated()) {
result = separatedAmount();
} else if (e.isRetired()) {
result = retiredAmount();
} else {
result = normalPay(e);
}
return result;
}
Solution
Task 3 ⭐ — Consolidate Conditional Expression (Python)¶
def disability_amount(seniority, months_disabled, is_part_time):
if seniority < 2: return 0
if months_disabled > 12: return 0
if is_part_time: return 0
return calculate()
Solution
Task 4 ⭐⭐ — Consolidate Duplicate Conditional Fragments (Java)¶
Task 5 ⭐⭐ — Replace Conditional with Polymorphism (Java)¶
class Bird {
String type; // "European", "African", "NorwegianBlue"
boolean isNailed;
double getSpeed() {
switch (type) {
case "European": return baseSpeed();
case "African": return baseSpeed() - loadFactor() * numberOfCoconuts();
case "NorwegianBlue": return isNailed ? 0 : baseSpeed(voltage());
default: throw new IllegalStateException();
}
}
}
Solution
abstract class Bird {
abstract double getSpeed();
double baseSpeed() { /* ... */ }
}
class European extends Bird {
double getSpeed() { return baseSpeed(); }
}
class African extends Bird {
private double loadFactor;
private int numberOfCoconuts;
double getSpeed() { return baseSpeed() - loadFactor * numberOfCoconuts; }
}
class NorwegianBlue extends Bird {
private boolean isNailed;
private double voltage;
double getSpeed() { return isNailed ? 0 : baseSpeed(voltage); }
double baseSpeed(double v) { /* uses voltage */ }
}
Task 6 ⭐⭐ — Introduce Null Object (Java)¶
Customer c = order.getCustomer();
String name;
if (c == null) name = "guest";
else name = c.getName();
Plan plan;
if (c == null || c.getPlan() == null) plan = freePlan();
else plan = c.getPlan();
Solution
Make `Order.getCustomer()` return NullCustomer when missing.Task 7 ⭐⭐ — Remove Control Flag (Java)¶
boolean found = false;
for (int i = 0; i < people.length && !found; i++) {
if (people[i].equals("Don")) {
sendAlert();
found = true;
}
if (people[i].equals("John")) {
sendAlert();
found = true;
}
}
Solution
Task 8 ⭐⭐ — Introduce Assertion (Java)¶
double getExpenseLimit() {
return (expenseLimit != NULL_EXPENSE) ? expenseLimit : primaryProject.memberExpenseLimit();
}
Solution
Better, for production:Task 9 ⭐⭐⭐ — Decision Table (Java)¶
A 50-line tier function:
String tier(double spend, int years) {
if (years > 10 && spend > 5000) return "GOLD";
if (spend > 2000) return "GOLD";
if (years > 5 && spend > 500) return "SILVER";
if (spend > 500) return "SILVER";
if (years > 3) return "BRONZE";
if (spend > 100) return "BRONZE";
return "NONE";
}
Solution
record Rule(java.util.function.BiPredicate<Double, Integer> match, String tier) {}
private static final List<Rule> RULES = List.of(
new Rule((spend, years) -> years > 10 && spend > 5000, "GOLD"),
new Rule((spend, years) -> spend > 2000, "GOLD"),
new Rule((spend, years) -> years > 5 && spend > 500, "SILVER"),
new Rule((spend, years) -> spend > 500, "SILVER"),
new Rule((spend, years) -> years > 3, "BRONZE"),
new Rule((spend, years) -> spend > 100, "BRONZE")
);
String tier(double spend, int years) {
return RULES.stream()
.filter(r -> r.match.test(spend, years))
.findFirst()
.map(Rule::tier)
.orElse("NONE");
}
Task 10 ⭐⭐⭐ — State Pattern (Java)¶
class Order {
String status; // "DRAFT", "SUBMITTED", "SHIPPED"
void cancel() {
if (status.equals("DRAFT")) status = "CANCELLED";
else if (status.equals("SUBMITTED")) { refund(); status = "CANCELLED"; }
else if (status.equals("SHIPPED")) throw new IllegalStateException();
}
void refund() { /* ... */ }
}
Solution
interface OrderStatus {
void cancel(Order o);
String name();
}
class Draft implements OrderStatus {
public void cancel(Order o) { o.setStatus(new Cancelled()); }
public String name() { return "DRAFT"; }
}
class Submitted implements OrderStatus {
public void cancel(Order o) { o.refund(); o.setStatus(new Cancelled()); }
public String name() { return "SUBMITTED"; }
}
class Shipped implements OrderStatus {
public void cancel(Order o) { throw new IllegalStateException(); }
public String name() { return "SHIPPED"; }
}
class Cancelled implements OrderStatus {
public void cancel(Order o) {}
public String name() { return "CANCELLED"; }
}
class Order {
private OrderStatus status = new Draft();
public void setStatus(OrderStatus s) { this.status = s; }
public void cancel() { status.cancel(this); }
public void refund() { /* ... */ }
}
Task 11 ⭐⭐ — Sealed Types + Pattern Matching (Java 21+)¶
abstract class Shape {}
class Circle extends Shape { double r; }
class Square extends Shape { double side; }
double area(Shape s) {
if (s instanceof Circle c) return Math.PI * c.r * c.r;
if (s instanceof Square sq) return sq.side * sq.side;
throw new IllegalStateException();
}
Solution
sealed interface Shape permits Circle, Square {}
record Circle(double r) implements Shape {}
record Square(double side) implements Shape {}
double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.r() * c.r();
case Square sq -> sq.side() * sq.side();
}; // exhaustive — adding a Triangle is a compile error here
}
Task 12 ⭐⭐⭐ — Combined refactoring (Python)¶
def process(order):
if order is not None:
if order.status == "draft":
if order.customer is not None:
if order.total > 0:
if order.customer.is_active:
if order.shipping_address is not None:
return submit(order)
else:
raise ValueError("no shipping address")
else:
raise ValueError("inactive customer")
else:
raise ValueError("zero total")
else:
raise ValueError("no customer")
else:
raise ValueError("not draft")
else:
raise ValueError("null order")
Apply 3+ techniques.
Solution
def process(order):
if order is None: raise ValueError("null order")
if order.status != "draft": raise ValueError("not draft")
if order.customer is None: raise ValueError("no customer")
if order.total <= 0: raise ValueError("zero total")
if not order.customer.is_active: raise ValueError("inactive customer")
if order.shipping_address is None: raise ValueError("no shipping address")
return submit(order)
def process(order):
validate(order)
return submit(order)
def validate(order):
require(order is not None, "null order")
require(order.status == "draft", "not draft")
require(order.customer is not None, "no customer")
require(order.total > 0, "zero total")
require(order.customer.is_active, "inactive customer")
require(order.shipping_address is not None, "no shipping address")
def require(cond, msg):
if not cond:
raise ValueError(msg)
Self-check¶
- ☑ I can flatten nested conditionals with guard clauses.
- ☑ I can identify when polymorphism replaces a switch.
- ☑ I can pick between Null Object, Optional, and throwing.
- ☑ I can introduce assertions appropriately.
- ☑ I can convert a chain of ifs into a decision table.
Next¶
- find-bug.md — wrong refactors
- optimize.md — perf
- interview.md — review