Handling Custom FhY Exceptions: Error Policy#
Hi! Welcome to our sixth FhY design blog post. You may view our previous blog post here.
Preface: Rationale for defining a Custom FhY Error Policy#
Soon, we will have non-developers use our software. Standardizing error reporting is therefore important to facilitate easier debugging. Ultimately what we want is accessible documentation for users (and developers) to be able to quickly identify, diagnose, and handle what exceptions may be raised when using FhY software. Furthermore, exceptions should be raised with clear and helpful diagnostic messages. At the same time, we need a policy to tightly control overzealous use of custom errors, which if unchecked, could grow untenable as the project develops. To meet these requirements, we could employ two different strategies:
Define all custom exceptions in a single module.
This makes it really easy to observe what custom errors are available. If there is any reasonable overlap in intended use, we can easily identify the issue and replace with an appropriate existing error.
One minor downside of this approach is that we don’t know exactly where each custom exception is being used (by looking at the error module alone). This may make it difficult to discern if errors are too specific in use.
Create a wrapper to register custom errors.
Custom errors are defined closer to where they are used throughout the FhY package. Inevitably, some custom errors need to be specific to submodules. This may make it easier to review where such errors are being used, making it easier to discern if an error is too limited in scope.
Creates a mapping to define all custom FhY exceptions. Furthermore, it also allows us to register builtin Python exceptions (especially if not all are used).
What does TVM do?#
For reference, TVM defines an Exception Policy. By design, TVM uses a combination of both methods outlined above. All custom errors are defined within a single module, error.py, and are registered using a wrapper. In part, the reason for also using a wrapper to register errors that are already defined within a single module, is because a majority of the the software is developed in C++. Since the python code is largely bindings, they register both custom and builtin python exceptions.
When deciding what error to raise, they encourage developers to use current existing errors. Exceptions defined within the error module also provide example usage within the docstrings, which provides guidance on message composition. For readability (consistency), they encourage developers to copy the provided example from the custom exception docstrings.
In the event it makes sense to create a new exception, a proposal must be written to describe the usage with an example. The proposed error should both differ from currently defined errors, and have decent scope of usage. On acceptance of a new custom error we have a sequence of events:
Add the new error to the tvm.error module, with clear documents (example).
Update (or register) the new custom Exception type.
Extrapolate the usage of the new exception to the existing code base.
FhY Custom Error Policy: Decision made#
FhY will also employ this dual strategy. All custom errors will be centralized and defined within a single module (fhy/error.py). Docstrings of the errors must provide a succinct description of the intended use followed by an example message composition to maintain consistency between developers.
All custom errors will be registered using a simple wrapper, register_error, to collect exception types and docstrings, making it easy to view all available exceptions. This latter part may be overkill, since we are not creating a method to retrieve errors by name, but does allow us to register builtin python exceptions that are used throughout the source code.
FhY Error Messaging Formatting Guidance#
Creating custom error types is just one way to make raised errors more clear to the client. The message accompanying the exception is just as important. For now, the documentation of each custom exception will provide an example message to define its usage. Multiple examples may be provided. We also provide general exception message guidance below:
Where possible and relevant, provide a description between expected and received values. General format of the message in this case should first describe the expectation, then provide the object received after a colon :. See the example below for raising a TypeError:
if not isinstance(value, str): raise TypeError(f"Expected 'value' argument type 'str'. Received: {type(value)}")
When describing errors derived from input source code, the error message should define the location the problem occurred to make it easier for the client to debug.
if problematic_syntax: raise SyntaxError(f"<Concise Reason Syntax is wrong>. Line {line_no}:{column_no}")
We will be providing more general guidance on constructing an error message soon.
Release Date: Thursday May 2nd, 2024
Last Updated: Thursday May 24th, 2024
Post Author(s): Jason C Del Rio