Concepts / Type System in Error Handling

Type System in Error Handling

The type system helps with error handling before any error crosses a boundary. Well-chosen types reduce the number of mistakes that can be made, and they give the compiler more chances to catch the rest.

  • Programming

When a Program Outgrows One File

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. This article asks what changes when those two places are no longer the same, and why that makes planning ahead so important.

Following One Error Through the Program

Suppose a team builds an application that depends on a library for a particular task. Early in the project, the library is small and only a few people call it. Now follow a single error from start to finish. The application calls the library, the library calls deeper code, and somewhere in that deeper code the problem is first noticed.

Application codecalls the libraryLibrary routinecalls deeper codeDetection pointnotices the problemHandling decisiondecides what it meanscallscallsproblem reported back
When an error is detected in a deeply nested piece of code, the question is how control moves back to the place in the application that decides what the error means. The source describes the general separation but not a specific mechanism, so this shows the shape of the chain only.

Read the diagram from top to bottom. The problem is noticed at the bottom of the chain, inside the library, but the response belongs at the top, inside the application. The detection point and the handling point are separated by one or more layers of calls, and the error has to get from one to the other.

Why Detection and Handling Drift Apart

Modularity and abstraction are central goals of C++ design. They split a program into parts that hide their internal details. One effect of this, especially when libraries are used, is that the point where a run-time error can be detected is no longer the same as the point where it can be handled. A run-time error is noticed in one place, and the response to it may need to happen in another.

Applicationowns the responseLibrary boundaryhides internal detailsDetection pointinside the libraryHandling pointin the calling codeusescontainscontains
The detection point sits inside the library, behind its boundary. The handling decision sits in the application that called the library. The abstraction layer hides the detection details from the application.

Tracing Who Owns Each Decision

What do you think happens?

In a program where a library notices a problem, which code should decide what that problem means?

  • The library code that noticed the problem

  • The application that called the library

Reveal answer
Answer:

The application that called the library

The source says the code that notices a problem may be inside a library, while the decision about what the problem means belongs to the application that called it.

Worked example

Tracing one library error through three roles

A team's application calls a library to perform a task. Partway through, the library finds that something is wrong. Identify where the problem is detected, where the library's internal details stop, and where the response is decided.

  1. 1
    Locate the detection

    The problem is noticed inside the library's deeper code, which the application never sees directly.

  2. 2
    Respect the boundary

    The library's abstraction hides its internal details, so the application works with the library's interface rather than its internal code.

  3. 3
    Locate the decision

    The application that called the library decides what the problem means for the program as a whole.

Result

The detection point and the handling point are different locations with different responsibilities. The program needs a shared understanding between them about what counts as an error and how errors are to be treated.

How the Type System Prevents Mistakes

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 alternative the source describes is to avoid building applications only from built-in types such as char, int, and double, and from statements such as if, while, and for. Instead, 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 as types, and sort(), find_if(), and draw_all() as algorithms.

char, int, doublebuilt-in typesstring, map, regexapplication typesif, while, forbasic statementssort, find_if,draw_allalgorithms for those types
The left column lists the most basic types and statements. The right column lists the higher-level types and algorithms the source gives as examples of what a programmer can build instead.

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. The source's concrete illustration 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.

Key point

The type system helps with error handling before any error crosses a boundary. Well-chosen types reduce the number of mistakes that can be made, and they give the compiler more chances to catch the rest.

Mistakes That Blur the Boundary

  • Common mistake

    Assuming the code that detects a problem also decides what it means

    Why it fails

    The source says detection and handling are different locations with different responsibilities. The author of a library may notice a problem that only the calling application can interpret.

    Do this instead

    Decide up front which layer is responsible for each kind of error, and document that split.

  • Common mistake

    Building application logic only from the most basic types and statements

    Why it fails

    Every piece assembled by hand is a place where something can go wrong, and the compiler has fewer chances to flag mismatches.

    Do this instead

    Introduce application-level types and algorithms so that the type system can help catch mismatches.

  • Common mistake

    Deferring the error-handling strategy until the program is large

    Why it fails

    As programs grow and libraries are used more heavily, detection points and handling points multiply, and an unplanned approach becomes hard to reason about.

    Do this instead

    Design and articulate the strategy early, while the program is still small enough to keep its error flow in mind.

  • Common mistake

    Treating an informal approach as sufficient once several people and libraries depend on the same code

Designing 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 earlier. 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.

  1. 1
    Decide, before the code grows, what the standards for handling errors will be
  2. 2
    Write those decisions down so that the strategy is stated clearly rather than left implicit in the code
  3. 3
    Share the written strategy with everyone who builds or uses the library and the application, so they hold the same expectations
Best practice

Write the error-handling strategy down while the program is still small. A short written statement of what counts as an error and which layer responds to it is far cheaper to create early than to reconstruct after many files and libraries exist.

Practice Locating the Two Points

MEDIUM

A team's application uses a library to read data. The library notices that the data is malformed while it is working deep inside its own code. Identify the detection point, the handling point, and the boundary that hides the library's details. Explain why the library author may not be the right person to decide what the malformed data means for the application.

Hints
  • Look for the place where the problem is first noticed, which is inside the library.
  • Ask which code has the responsibility for deciding what the problem means for the whole program.
  • Recall the source's statement that the decision about what the problem means belongs to the application that called the library.

Key Takeaways

  • A run-time error can be detected in one place and handled in another, because modularity and abstraction hide internal details behind boundaries, especially when libraries are used.
  • The detection point and the handling point have different responsibilities, so a program needs shared expectations about what counts as an error and how errors are treated.
  • The type system simplifies programming, limits opportunities for mistakes, and increases the compiler's chances of catching errors, by letting programmers build application-level types and algorithms.
  • As programs grow and libraries are used extensively, an error-handling strategy should be designed and articulated early in development.

Related concepts

More concepts starting with T