Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Level 4 – Layered Bugs (≈ 10 minutes, stretch)

Concepts: layered bugs, unexpected control flow, combining debugger tools, fixing bugs without breaking other behavior.

This Level uses a small “level loader” that:

  • Reads a level number from a (fake) string.
  • Loads some enemy counts.
  • Computes total difficulty.

The code has:

  • A runtime bug (exception).
  • A logic bug.
  • A bug where stepping into another file is useful.

1. Setup the files

We’ll add two more files:

  • LevelLoader.java
  • InputParser.java

InputParser.java

public class InputParser {

    // Parses level from an input string like "level:3"
    public static int parseLevel(String input) {
        // BUG 1: assumes the string always has "level:" and a valid number
        String[] parts = input.split(":");
        String numberPart = parts[1]; // can throw if input is wrong

        // BUG 2: doesn't trim spaces, can cause NumberFormatException
        return Integer.parseInt(numberPart);
    }
}

LevelLoader.java

public class LevelLoader {

    // Returns an array of enemy counts for the given level.
    public static int[] loadEnemiesForLevel(int level) {
        if (level == 1) {
            return new int[] {2, 3};
        } else if (level == 2) {
            return new int[] {4, 4, 5};
        } else if (level == 3) {
            return new int[] {10};
        } else {
            // BUG 3: unexpected control flow – returns null instead of empty array
            return null;
        }
    }

    // Computes a "difficulty" score from enemy counts.
    public static int computeDifficulty(int[] enemies) {
        int difficulty = 0;

        // BUG 4: off‑by‑one and assumes enemies is never null
        for (int i = 0; i <= enemies.length; i++) {
            difficulty += enemies[i] * (i + 1);
        }

        return difficulty;
    }
}

Update GameApp.java

Add this method to the bottom of GameApp (inside the class, after printPlayerStats):

    private static void demoLevels() {
        System.out.println("=== Level Loader Demo ===");

        String input = "level: 4"; // note the space before 4
        System.out.println("Parsing input: \"" + input + "\"");

        int level = InputParser.parseLevel(input);
        System.out.println("Parsed level: " + level);

        int[] enemies = LevelLoader.loadEnemiesForLevel(level);
        int difficulty = LevelLoader.computeDifficulty(enemies);

        System.out.println("Total difficulty for level " + level + ": " + difficulty);
    }

Then, in main, after the mystery player block, add:

        demoLevels();

Make sure GameApp.java now:

  • Uses Player, ScoreUtils, LevelLoader, and InputParser.

2. Run and observe

  1. Open GameApp.java.
  2. Click Run.

Expected symptoms:

  • The scoreboard section should behave as in Level 3 (assuming you fixed it).
  • The Level Loader demo will likely:
    • Throw a NumberFormatException or NullPointerException.
    • Or print a strange difficulty.

Look at:

  • The exception type.
  • The stack trace: which method and line caused it?

3. Step through the layered flow

Use the debugger to trace the whole path:

  1. Set a breakpoint at the first line of demoLevels.
  2. Click Debug.
  3. When paused:
    • Step Over the print statements.
    • Use Step Into on InputParser.parseLevel(input).

Inside parseLevel:

  1. Inspect input, parts, and numberPart.
  2. Step line‑by‑line and watch for:
    • ArrayIndexOutOfBoundsException on parts[1].
    • NumberFormatException on Integer.parseInt.

Once you understand the problem, stop debugging and fix parseLevel to:

  • Validate the input format.
  • Trim whitespace.
  • Return a reasonable default (for example 1) or throw a clearer exception.

Run again with the debugger to confirm the parsing step works.


4. Follow the null / off‑by‑one bugs

Next, set a breakpoint on:

int[] enemies = LevelLoader.loadEnemiesForLevel(level);
  1. Debug again and Step Into LevelLoader.loadEnemiesForLevel.
  2. For the given level, see which branch runs and what it returns.
  3. Step back to demoLevels and then Step Into computeDifficulty.

Inside computeDifficulty:

  1. Watch the values of i, enemies.length, and difficulty.
  2. See what happens when i equals enemies.length.
  3. If enemies is null, notice how that changes things.

Fix the bugs by:

  • Making loadEnemiesForLevel return a non‑null array, even for unknown levels (for example, an empty int[]).
  • Adjusting the for loop in computeDifficulty so it:
    • Does not go out of bounds.
    • Handles enemies being null or empty safely.

Re‑run and confirm:

  • No exceptions occur.
  • The printed difficulty matches your expectations.

5. Avoid introducing new bugs

After each fix, re‑run the whole program, not just the Level Loader demo:

  • Check that:
    • The scoreboard output (from Level 3) still looks correct.
    • No new exceptions appear.

This is a realistic part of debugging:

  • Fix one bug.
  • Run the full program.
  • Make sure you didn’t break something else.

6. Reflection (Level 4)

Short prompts:

  • What kinds of bugs did you see in this Level (compile‑time, runtime, logic)?
  • How did stepping into helper methods (parseLevel, loadEnemiesForLevel, computeDifficulty) help?
  • What habits can help avoid layered bugs (e.g., validating inputs, returning empty collections instead of null)?

You can compare your fixes to the suggested ones in Solutions & Fixes.