انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-012-L05

ترويض الشروط المتداخلة

  • الدرس 5 من 6
  • 11 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

Real rules come with conditions inside conditions: "if the user is logged in, and if the account is active, and if the quantity is positive, then accept the order". Written naively, that becomes an if inside an if inside an if, each level pushing the code further to the right. By the third level nobody can say with confidence which road a given input takes.

This lesson teaches three tools to flatten such code: combining tests with && and ||, early exits that reject bad cases first, and naming a complicated test with a boolean variable. It also teaches you to audit a set of conditions for overlapping and unreachable branches, which is how many logic bugs hide.

It builds on all earlier lessons: if/else (L1), chains and boundaries (L2), and switch (L3 and L4), plus the logical operators from Module 10. The next lesson uses these skills to code a realistic shipping-cost rule.

Learning Objectives

By the end of this lesson you will be able to:

  • Replace nested if statements with && where the structure allows it.
  • Rewrite a nested decision as a flat sequence using early exits (guard clauses).
  • Name a complicated condition with a boolean variable to make it readable.
  • Identify an unreachable branch and an overlapping pair of conditions.
  • Explain how an else pairs with an if when braces are missing.
  • Decide when nesting is justified and when it is a warning sign.

Written Explanation

Why deep nesting hurts

Each nesting level adds a fact you must hold in your head: "I am here only if A is true, and B is true, and...". Three levels deep, the actual action sits far from the conditions that lead to it, and the else branches are scattered at the bottom, far from the if they belong to. Beginners add a level for every new rule. Experienced developers ask how to remove one.

Tool 1: combine with && and ||

Two nested if statements with no else between them are the same as one if with &&:

java
// nested
if (a) {
    if (b) {
        run();
    }
}
// equivalent
if (a && b) {
    run();
}

The first version runs run() only when a is true and, inside that, b is true. Both must hold, which is exactly &&. Because && is short-circuit, b is not even evaluated when a is false, so you can safely write x != 0 && 10 / x > 2.

Use || when several different conditions lead to the same action: if (isMember || hasCoupon). Mix them with parentheses, because && binds tighter than ||.

This tool works when all the cases lead to the same action. It does not help when each failed test needs its own message, as in the next tool.

Tool 2: early exits (guard clauses)

When each condition has a different failure message, deal with the failures first and leave the normal path at the end. This is called an early exit or guard clause: test for the bad case, report it, and leave.

In a method you leave with return. You have not met methods yet (they start in a later module), but main is a method, and a return; inside it simply ends the program. We use that here for the pattern; the same style carries over into every method you will write later.

The shape:

java
if (badCase1) { report; return; }
if (badCase2) { report; return; }
// all checks passed: normal work here

The normal path sits at the lowest indentation, and every check is one line away from its message. Compare this with the nested version in Example 3.

Tool 3: name the condition

A long test is easier to read when each part has a name:

java
boolean isSenior = age >= 65;
boolean isChild = age < 12;
boolean discountEligible = member && (isSenior || isChild);
if (discountEligible) { ... }

A reader now sees the rule, not the arithmetic. A named variable also gives you somewhere to put a comment and a place to print while debugging.

How else pairs when braces are missing

An else always belongs to the nearest unmatched if before it. With braces this is obvious; without them, your indentation can lie. Example 2 shows it. Another reason for the "always use braces" rule from Lesson 1.

Auditing conditions: unreachable and overlapping branches

When you finish a decision, check every pair of branches.

  • Unreachable branch: a branch that can never run because earlier tests catch every value that could reach it. In if (x > 5) {...} else if (x > 10) {...}, any x above 10 is already above 5, so the second block is dead code.
  • Overlap: two conditions that can be true for the same value. In a chain the earlier one wins silently; in separate if statements both run. Either way, the author probably did not mean it.
  • Gap: a value that no branch handles (Lesson 2).

Java's compiler does not warn about these logic errors, even though the dead code is easy to see. (It does reject statements that are provably unreachable in specific structural ways, such as code after return, but not an if branch whose condition happens to be impossible.) The only defence is to test: for each branch, find an input that reaches it. If you cannot find one, the branch is dead.

Examples

Example 1: && instead of nesting (simple)

java
boolean a = true, b = true;
if (a) {
    if (b) {
        System.out.println("nested");
    }
}
if (a && b) {
    System.out.println("combined");
}

Output:

text
nested
combined

Both forms behave the same. The second is shorter and says in one line what must hold.

Example 2: when indentation lies (practical)

java
int p = 5;
if (p > 0)
    if (p > 10)
        System.out.println("big");
else
    System.out.println("not positive?");
System.out.println("end");

Real output:

text
not positive?
end

The author's layout suggests that the else belongs to p > 0. It does not: it pairs with the nearest if, p > 10. Since 5 is positive but not above 10, the program prints not positive?, which is nonsense for a positive number. With braces and correct indentation the intent becomes explicit:

java
if (p > 0) {
    if (p > 10) {
        System.out.println("big");
    }
} else {
    System.out.println("not positive");
}

Example 3: validating an order, nested versus flat (realistic)

The rules: the customer must be logged in, the account must be active, and the quantity must be positive. Each failure has its own message.

Nested version:

java
if (loggedIn) {
    if (active) {
        if (qty > 0) {
            System.out.println("Order accepted");
        } else {
            System.out.println("Bad quantity");
        }
    } else {
        System.out.println("Account inactive");
    }
} else {
    System.out.println("Please log in");
}

Flat version with early exits:

java
if (!loggedIn) {
    System.out.println("Please log in");
    return;
}
if (!active) {
    System.out.println("Account inactive");
    return;
}
if (qty <= 0) {
    System.out.println("Bad quantity");
    return;
}
System.out.println("Order accepted");

I ran both versions in one program. With loggedIn and active true and qty = 3, each printed Order accepted. With qty = 0, each printed Bad quantity (and the flat version's return ended the program). They agree on every input, but in the flat version each message sits next to its test and the normal path is the last line.

The flat version has one thing to watch: the tests are written as the opposite of the success condition (!loggedIn, qty <= 0). Check each opposite carefully; the complement of qty > 0 is qty <= 0, not qty < 0.

Backend Insight

Request handling code in a backend almost always starts with a block of guard clauses: is the user authenticated, is the user allowed, is the input valid, does the thing exist? Only then does the real work happen. This shape is so common that reviewers expect it, and deep nesting in such code is read as a warning: someone probably added rules on top of rules without restructuring.

A second reason to flatten is testing. Each guard is one rule with one message, so each can be tested with one input. A five-level nest has combinations that nobody will test. And a third reason is change: when the product team adds a new rule ("blocked accounts cannot order"), a flat structure takes one more guard, while a nest takes a new level and rewrites the else branches around it.

Common Mistakes

Mistake 1: Nesting by reflex

  • Incorrect: a new if level for every rule, three or more levels deep.
  • Correct: && for rules with the same outcome, guard clauses for rules with their own messages. As a rough guide, if you reach three levels of indentation, stop and flatten.

Mistake 2: Wrongly negating a condition

  • Incorrect: the opposite of qty > 0 written as qty < 0. The value 0 then gets through.
  • Correct: qty <= 0. For a && b, the opposite is !a || !b (De Morgan's laws), not !a && !b.

When in doubt, test with the boundary value, here 0.

Mistake 3: Trusting indentation for else

See Example 2. Use braces and let a formatter or IDE re-indent your code; if the layout changes, you have found a misleading structure.

Mistake 4: Leaving an unreachable branch

  • Incorrect: if (age >= 0) {...} else if (age > 100) {...}. Values above 100 never reach the second test.
  • Correct: test the narrower condition first, or remove the dead branch.

Mistake 5: Overusing a boolean name

boolean flag = a && b; explains nothing. Name the idea (canCheckout), not the mechanism.

Exercises

Level A: Foundation

A1. Rewrite as one if:

java
if (loggedIn) {
    if (isAdmin) {
        showPanel();
    }
}
أظهر الحل A1

A1. if (loggedIn && isAdmin) { showPanel(); }

A2. Which branch is unreachable? Explain.

java
if (n >= 0) { System.out.println("non-negative"); }
else if (n > 100) { System.out.println("large"); }
else { System.out.println("negative"); }
أظهر الحل A2

A2. n > 100 is unreachable: every n above 100 is already non-negative, so the first branch catches it. The else is reached only by negative values. Fix: test n > 100 first, or drop that branch.

A3. What is the opposite (complement) of price > 0 && stock > 0?

أظهر الحل A3

A3. price <= 0 || stock <= 0. By De Morgan's laws, the negation of an && is an || of the negated parts.

Level B: Application

B1. Predict the output without running it, then check by running:

java
int n = 4;
if (n > 0)
    if (n > 10)
        System.out.println("A");
else
    System.out.println("B");
أظهر الحل B1

B1. B. The else pairs with the nearest if, n > 10. With n = 4, the outer test is true, the inner false, so the else runs. The layout suggests otherwise, which is why braces matter.

B2. Rewrite this nested code with early exits. Use System.out.println for each message and return; after it:

java
if (age >= 18) {
    if (idChecked) {
        System.out.println("Entry allowed");
    } else {
        System.out.println("Show ID");
    }
} else {
    System.out.println("Too young");
}
أظهر الحل B2

B2.

java
if (age < 18) {
    System.out.println("Too young");
    return;
}
if (!idChecked) {
    System.out.println("Show ID");
    return;
}
System.out.println("Entry allowed");

B3. Define boolean variables to simplify if (day == 6 || day == 7 || holiday) { ... } and explain what you gained.

أظهر الحل B3

B3.

java
boolean weekend = day == 6 || day == 7;
if (weekend || holiday) { ... }

The condition now reads like the rule. You also have a named piece you can reuse in other conditions and print when debugging.

Level C: Challenge

C1. A discount rule: members get 10 percent. Non-members get 5 percent if they buy 5 or more items. Others get none. Write it as a flat chain with a double discount. Then list the four combinations of member and quantity (above and below 5) and the expected result of each.

أظهر الحل C1

C1.

java
double discount;
if (member) {
    discount = 0.10;
} else if (qty >= 5) {
    discount = 0.05;
} else {
    discount = 0;
}

Expected: member with any quantity gives 0.10, regardless of count; non-member with 5 or more gives 0.05; non-member with fewer than 5 gives 0. The third row demonstrates that a flat chain needs no member && qty < 5 in the last branch, because earlier tests already filtered members.

C2. A teammate wrote:

java
if (temp > 30) { status = "hot"; }
if (temp > 20) { status = "warm"; }
if (temp > 10) { status = "mild"; }
else { status = "cold"; }

State what status 35 gets and why. Then fix the code. Which concept from this lesson is involved?

أظهر الحل C2

C2. For 35, the first if sets hot, then the second sets warm, then the third sets mild: the last assignment wins, so 35 ends as mild. These are separate if statements, so overlapping conditions all run, and only the last else belongs to the last if. The fix is an else if chain: if (temp > 30) ... else if (temp > 20) ... else if (temp > 10) ... else .... Concepts: overlap and chain order.

Mini Challenge

A conference app accepts a registration only when all of these hold: a boolean nameGiven is true, the age is at least 16, the ticket count is between 1 and 4 inclusive, and, if the ticket count is 3 or more, the attendee agreed to the group terms (agreedTerms). Each failure needs its own message.

  1. Write the flat version with early exits and one println per failure.
  2. Write a table with at least 8 test inputs, including every boundary (age 15 and 16, tickets 0, 1, 4, 5, and 2 versus 3 with and without the terms).
  3. Predict each outcome by hand, then run.

Check your work: no nesting deeper than one level, and each guard is tested by at least one input that triggers it.

Summary & Next Lesson

Key takeaways

  • Nested if with no else between them equals one if with &&.
  • Guard clauses (early exits) handle each failure first and leave the normal path flat.
  • Name complicated conditions with boolean variables.
  • An else pairs with the nearest if; braces make the pairing visible.
  • Audit every pair of branches for unreachable code, overlaps, and gaps; the compiler will not do it for you.

Next lesson

Time to put it all together. The next lesson, "Demo: shipping cost rules and their boundaries", turns written shipping rules into clean conditions and builds a test table around every boundary, preparing you for the shipping lab.

Self-check

  1. Which operator replaces an if inside an if with no else?
  2. What does return; do inside main?
  3. What is wrong with writing the opposite of a && b as !a && !b?
  4. In if (x > 5) {...} else if (x > 10) {...}, when does the second block run?
  5. To which if does an else belong?
أظهر الإجابات
  1. &&.
  2. It ends the main method, so the program ends.
  3. The correct opposite is !a || !b; the wrong version is true only when both are false.
  4. Never; any x above 10 is already above 5.
  5. The nearest unmatched if before it, regardless of indentation.

References

  • Oracle, The Java Language Specification, Java SE 21, Chapter 14. Supports the rule that an else belongs to the innermost if it can pair with (the "dangling else" rule).
  • Outputs were produced by compiling and running the examples with JDK 21. The claim that the compiler does not flag an impossible else if condition was confirmed by compiling such code without errors; the exact unreachable-statement rules (JLS section 14.22) were not read in full, so that claim is limited to what I observed.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.