Modularity and Abstraction in Error Handling
Error handling reaches beyond language features into programming techniques and tools, and C++ offers help through its type system.
Two Places for One Problem
Imagine a program small enough to fit in one file. When something goes wrong, the code that noticed the problem and the code that decides what to do about it are usually close together. You can read from top to bottom and see both. Now imagine the same program has grown into dozens of files, uses several libraries written by other people, and is maintained by a team. The code that notices a problem may now be buried inside a library, while the decision about what the problem means belongs to the application that called it.
What do you think happens?
When a library notices that something has gone wrong, which part of the program should decide what that problem means?
Reveal answer
The application that called the library
The source describes the code that notices a problem as possibly buried inside a library, while the decision about what the problem means belongs to the application that called it. The noticing and the deciding happen in different places.
This article asks a simple question: what changes when those two places are no longer the same, and why does that make planning ahead so important? The short answer is that the separation between detection and handling is not an accident. It is a direct consequence of how modern programs are organized into parts that hide their internal details.
Tracing an Error Through Layers
The diagram below follows a run-time error from the work it was doing up to the point where a decision is made. Read it from top to bottom: the application calls into a library, the library's internal work code runs, and the error is first detected at a point inside that work. The handling point is where the program finally decides what the error means. The diagram shows the general shape of that relationship. It does not show a specific mechanism for passing the error along, because the source does not describe one.
Notice that the detection point and the handling point sit at different levels of the structure. The detection point is deep inside the work code, and the handling point is back in the application. Whatever path the error information takes between them, the program has to get the information from one place to the other, and that path crosses the library boundary.
What Modularity and Abstraction Hide
Modularity and abstraction, which the source describes as central goals of C++ design, split a program into parts that hide their internal details. A library exposes what it offers to the outside world, while the code behind that interface stays out of sight. The calling application can use the library without knowing how the library does its work.
The hierarchy shows the two roles separated by a layer. The library sits between the application and the place where the problem is noticed. The application is the layer that knows enough about the overall task to decide what the problem means for the user. The library knows enough about its own work to notice that something has gone wrong, but it does not necessarily know what the application should do about it.
Why Detection and Handling Diverge
The central idea of this article is a separation. The point where a run-time error can be detected is not always the same as the point where it can be handled. A run-time error may be noticed in one place, and the response to it may need to happen in another. This separation is one of the effects of modularity and abstraction, especially when libraries are used.
Notice what this separation implies. Whoever writes the code that detects a problem may not be the person who decides how the program should respond. The detection point and the handling point have different responsibilities. Because of that, a program needs some shared understanding between them about what counts as an error and how errors are to be treated. Without that shared understanding, the detecting code and the handling code may each assume something different.
Worked Trace: One Error, Three Layers
Following a bad value through a library
An application uses a library to read a number from user input. Inside the library, a helper routine finds that the input is not in the expected form. Trace where the problem is detected, where it is decided upon, and why the two are different places.
- 1Step 1: The application makes the call
The application asks the library to read a number. At this point the application knows only that it wants a number back. It does not know how the library parses text.
- 2Step 2: The library does its internal work
Inside the library, a helper routine examines the input. The internal details of this work are hidden from the application by the library's abstraction.
- 3Step 3: The problem is detected
The helper routine notices that the input is not in the expected form. This is the detection point. It is deep inside the library, far from the place where the program decides what to do.
- 4Step 4: The problem must reach the decision point
The information that something went wrong has to get back to the application. The source does not specify the mechanism, so the trace records only that the information must cross the library boundary.
- 5Step 5: The application decides what it means
The application, which knows the wider purpose of the task, decides how to respond. This is the handling point. It may be a different response from the one the library would have chosen on its own.
The error was detected inside the library and handled in the application. The two places differ because modularity hides the library's details, and the decision about meaning belongs to the caller.
The Type System as a First Line of Defense
Before worrying about errors that cross boundaries, consider what the type system does to prevent mistakes in the first place. A program built only from the most basic types and statements has to be assembled piece by piece, and every piece is a place where something can go wrong. The source describes an alternative: instead of building applications only from built-in types such as char, int, and double, and from statements such as if, while, and for, a programmer builds additional types suited to the application, along with algorithms suited to those types. Examples named in the source include string, map, and regex, and the algorithms sort(), find_if(), and draw_all().
The source gives two benefits of this approach. Higher-level constructs simplify programming and limit the opportunities for mistakes. They also increase the compiler's chances of catching errors. A concrete illustration in the source is that a programmer is unlikely to apply a tree traversal to a dialog box, because the types involved do not fit together naturally. The type system makes that kind of mismatch easier to avoid, and easier for the compiler to flag before the program runs.
Planning the Strategy Early
The source recommends a specific habit: design and articulate a strategy for error handling early in the development of a program. The reasoning follows from the separation described above. When a program is small, one person can keep the whole flow of errors in mind, and an informal approach may be enough. As programs grow and libraries are used more heavily, the number of detection points and handling points multiplies, and an unplanned approach becomes harder to reason about.
- 1Decide, before the code grows, what the standards for handling errors will be.
- 2Write those decisions down so that everyone who builds or uses the library and the application shares the same expectations.
- 3Agree on what counts as an error, since the detection point and the handling point must share that understanding.
- 4Revisit the strategy as libraries are added, because each new library adds another boundary between detection and handling.
The source frames this as something to design and articulate. That wording suggests the strategy should be stated clearly, not left implicit in the code. A strategy that exists only in one developer's head is hard to share, and it becomes harder to recover once the program has many files and many contributors.
Mistakes Beginners Make About Error Location
Common mistake Assuming the place where a problem is noticed is always the place where it should be fixed
What it looks likeA library detects bad input deep inside its parsing work, and the programmer looks only in the library for the response.
Why it failsThe source states that the detection point and the handling point can be different locations. The decision about what the problem means may belong to the application that called the library.
Do this insteadTrace where the error is detected and separately ask which layer knows enough to decide how to respond.
Common mistake Leaving error-handling decisions unstated until the program is large
What it looks likeA team builds a small program with no written rules for errors, then adds several libraries, and each contributor handles problems differently.
Why it failsThe source recommends designing and articulating a strategy early. As libraries multiply, the number of detection and handling points grows, and unstated expectations diverge.
Do this insteadWrite down the error-handling standards early and share them with everyone who builds or uses the library and application.
Common mistake Treating the type system as unrelated to error handling
What it looks likeA programmer builds an application entirely from basic types and statements and assumes that the compiler has no way to help.
Why it failsThe source says the type system simplifies programming, limits opportunities for mistakes, and increases the compiler's chances of catching errors.
Do this insteadUse higher-level types and algorithms suited to the application, so that mismatches are easier to avoid and easier for the compiler to flag.
Practice: Locating Detection and Handling
A team's application uses a library for a particular task. Inside the library, a routine notices that a value is out of range. The application shows a message to the user and chooses to retry the operation. Identify the detection point, the handling point, and the layer boundary between them. Then explain in one or two sentences why these are different places.
Hints
- Ask first which code noticed the problem, and which code decided what the problem means.
- The source ties the separation to the library boundary, so look for the point where one part of the program hands control or information to another.
- Compare your answer with the worked trace: the detection happened inside the library, and the meaning was decided by the application.
Key Takeaways
- A run-time error can be detected in one place and handled in another; modularity and abstraction, especially when libraries are used, create that separation.
- The application that calls a library often holds the decision about what a problem means, even when the problem is noticed deep inside the library.
- The type system simplifies programming, limits opportunities for mistakes, and increases the compiler's chances of catching errors.
- A strategy for error handling should be designed and articulated early, because the number of detection and handling points grows as programs and libraries grow.