Exception Handling
Lecture Notes for CS 190
Winter 2018
John Ousterhout
- Reading: Chapter 9 of book
- Exceptions come from many sources:
- From above:
- Bad input from user or client
- Misconfiguration and operator errors
- From below: underlying system facilities don't work as desired:
- Disk I/O error
- Can't open file (wrong permissions, missing directory, etc.)
- Out of memory
- Network socket already in use
- From peers in a distributed system:
- Server crashes
- Slow communication
- Lost network packets
- From ourselves: internal bugs
- Errors and exceptions are a major source of complexity and bugs
- They account for a lot of code in large systems
- They disrupt normal code flow:
- They happen in the middle of other activities
- Something didn't work like you expected
- Hard to figure out how to handle them
- May not be able to complete work in progress
- Cleanup/recovery may be tricky
- Language support is clunky
- Verbose
- Makes code hard to read
- Hard to test
- Don't occur very often in running applications
- Programmers often make the exception problem worse:
- Defensive programming: throw exceptions for anything that
looks a tiny bit suspicious. More exceptions are better?
- Expediency: rather than figure out how to solve a problem,
just throw an exception, punt it to the next level
- Result: even more exceptions, many of which no-one really knows
how to handle.
- Key idea: reduce the number of places where exceptions must be
handled.
- Specific techniques:
- Whenever possible, define errors out of existence:
- Deleting variables in Tcl
- File deletion in Windows
- Bounds checks in Java substring method
- Mask exceptions (recover automatically so the exception doesn't
have to be reported)
- Collapse exceptions (handle several different cases with the
same code)
- Promote one exception to another (in RAMCloud, many exceptions
get promoted to "server crash").
- Reuse existing handler
- May be too inefficient for exceptions that happen frequently
- Defer reporting to a place where other exceptions could
already happen.
- Example: in RAMCloud, report RPC exceptions only on wait,
not send.
- Or, handle exception once in a higher-level method,
rather than multiple times in lower-level methods.
- Group exceptions into categories according to how they
will be handled.
- Find a code structure where each category is only handled
in one place.
- Advantages of collapsing:
- Simplifies code (fewer cases).
- Remaining handlers get invoked more often (will get
debugged).
- Just panic (crash app)?
- If there's not a viable way to handle it
- Example: running out of memory in malloc
- Or, throw an unchecked exception, which isn't handled except at the
very top level.
- If an exception can't be masked, it can be reported in 4 ways:
- Log a message and continue
- Return a special value
- Checked exception (caller must deal with it)
- Unchecked exception
- Think about how the caller will handle it:
- If the caller will almost always care about the exception,
might as well report it with a return value.
- If caller will almost never be able to recover from it, use
an unchecked exception
- Exceptions work better the farther they are thrown.
- Make lots of specific information available after exceptions:
- Include in exception message?
- Additional fields of exception?
- Or, output to system log
- Assertions versus exceptions:
- Assertions are for things that should absolutely never happen in
production
- An assertion failure means there is a bug in the code
- Assertion checking can be disabled during production for highest
performance
- How much internal error checking should an application do?
- One philosophy: check everything (tons of assertions)
- I tend to use assertions fairly lightly (assume that unit tests
will flush out most bugs)
- Must always check "less trusted" input
- Internal consistency checker (e.g. for B-tree structure)