0%

Errors as Part of Program Design · practice

Catch Errors at the Interface Boundary

If no along the way should catch an , someone eventually has to. The question this lesson answers is who.

The rule

Catch where you can do something about it.

That is not where the error happened, and it is usually not anywhere near it. It is wherever the program knows what the failure means for the person using it. In a program that is the entry point. On a web page it is the request handler. In a test it is the test runner.

Those places have a name: the boundary. Inside it is your domain, which computes and refuses. Outside it is a person, who needs a sentence rather than a .

A TypeError travels from domain code to the quiz boundary, where it becomes a useful message.
Try it

The bad response produced a readable line and the quiz carried on. Question still knows nothing about people, printing, or carrying on, and it did not have to change.

Catching specifically

Two rules keep a handler honest.

Catch the narrowest type that you can actually handle. except TypeError says you thought about TypeError. except Exception says you gave up, and it will also swallow the AttributeError from your own typo.

Never write a bare except: with nothing after it. It catches KeyboardInterrupt too, so it can swallow the usual keyboard request to stop the program.

There is one place a broad catch is right: the outermost handler of a long-running program, whose job is to log anything unexpected and keep serving. Even there it re-raises or records, never silently continues.

A Quiz.score() calls Question.points_for, which may raise TypeError. Where should the try go?

Swallowing

The failure mode of catching is not catching too little. It is this:

try:
    score = score + question.points_for(response)
except Exception:
    pass

The program continues with a score that is quietly wrong, and every trace of why is gone. This is worse than crashing, because a crash gets fixed.

If you catch something, do at least one of: tell someone, record it, use a fallback you can defend, or re-raise. pass is only defensible when the exception genuinely means “nothing to do here”, and then it deserves a comment saying so.

Cleaning up with finally

Sometimes something has to happen whether or not there was an error:

Try it
scored_run(questions, ["def", 42])

The finally block ran both times, including on the way out of the failure. That is what it is for: releasing something, closing something, or recording that an attempt happened, regardless of outcome.

finally does not handle the error. The exception carries on afterwards, which is usually right: cleaning up and deciding what a failure means are different jobs.

The exercise puts the boundary in one place and leaves the domain untouched.

Task

Put the handling at the boundary, and nowhere else.

run_quiz(questions, responses) is the boundary. It returns a (score, problems) pair, where problems is a of "<prompt>: <message>" , one per response it had to skip. A bad response is skipped and the quiz continues.

Catch and ValueError specifically. Do not catch Exception: a typo in your own code should still crash loudly rather than be reported as a learner’s bad answer.

Question.points_for and Attempt.record stay exactly as they are. Neither can decide what a failure means, so neither may gain a try.

The supplied audited_run shows the separate role of finally: it records how many responses were examined whether the run returns or an unexpected error continues outward. Read and run it, but keep your implementation task focused on run_quiz.