Unit 13 · lesson
Catch Where You Can Actually Decide What Happens Next
A catch block is useful when that layer has enough context to make a meaningful decision.
This is weak:
try {
// entire application
} catch (Exception ex) {
IO.println("Something went wrong");
}
It hides which operation failed, discards useful distinctions, and can make the program appear to continue safely when state may be incomplete.
Parse at an input boundary
A user-input layer understands what to do with invalid text:
int readScore(String text) {
try {
int score = Integer.parseInt(text);
if (score < 0 || score > 100) {
throw new IllegalArgumentException("score out of range");
}
return score;
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("score must be an integer: " + text, ex);
}
}
The method translates a low-level parsing problem into a domain-facing message while preserving the original cause.
You have not learned custom exception hierarchies yet, and you do not need one for every failure. The important design question is which layer can describe the failure most usefully.
Recovery versus translation versus propagation
A layer can:
Recover
It knows a safe alternate action, such as asking for new input.
Translate
It catches a low-level failure and throws a more meaningful higher-level exception while preserving the cause.
Propagate
It does not know how to recover, so it lets the failure travel to a caller that might.
Catching is not automatically superior to propagation.
Do not use exceptions for ordinary branches
If absence is a normal expected state, a normal conditional may communicate it better.
if (!map.containsKey(key)) {
IO.println("unknown key");
}
Do not deliberately trigger a failure merely to use catch as routine control flow when a direct operation communicates the rule.
Narrow the try block
Keep the try region focused around operations that can produce the failure you intend to handle.
Instead of wrapping 40 lines, isolate parsing:
int parsed;
try {
parsed = Integer.parseInt(text);
} catch (NumberFormatException ex) {
// input policy here
}
Now the catch's relationship to the risky operation is inspectable.
Design a failure policy
For a fictional command-line score importer, define what should happen when:
- one score line is nonnumeric;
- one score is 150;
- required data is missing;
- an internal invariant is violated.
For each choose recover, translate, or propagate, and explain why.
Evidence
Create one example where catching at the low-level method would be too early because the method lacks recovery context. Then move the handling to a layer that can actually decide what the user/system should see.
Your explanation should say what new context the higher layer has.