Suppose of having a library exposing the following piece of functionality:
public static class AwesomeHelpers
{
public static async Task<int> ComputeSomethingImportAsync(CalculationInputs input)
{
int result = 13;
// actual implementation is not relevant for this discussion
return result;
}
}
Suppose also that the calculation could fail for 3 different reasons, depending on external services used by the calculation algorithm:
- an external API returns a non success status code
- a record of data is missing in the database
- another external API returns a response containing invalid JSON (response content cannot be parsed as JSON)
The public API of the library either returns an integer which is the calculation result or throws a proper exception to communicate the calculation failure to the library users. The possible failures of the calculation process are only the ones listed above. Notice that there is not the possibility of a business failure of the calculation (e.g.: the calculation process cannot be completed based on a certain business rule); the only possible failures depend on errors at the level of external services, so throwing an exception to the library user seems the correct thing to do here.
Given this scenario, there are 2 main strategies I can think of:
- throw 3 different exception types, each one corresponding to one of the possible failures (e.g.:
FooApiErrorException, MissingRecordException, BarApiErrorException)
- define one custom exception type to be used each time something goes wrong with the calculation process (e.g.:
CalculationProcessException). In this case both the exception message and the InnerException property can be used to provide further details on the error.
In both cases the exceptions raised by the library need to be carefully documented (e.g.: XML documentation for Visual Studio intellisense).
Based on my experience and personal preference, the second strategy (defining one custom exception type to be used for all possible failures and specifying the error details with the message and the InnerException property) is the best solution, because it allows the library user to write simpler code.
What I mean is that this code
try
{
result = await AwesomeHelpers.ComputeSomethingImportAsync(input);
}
catch(CalculationProcessException exception)
{
// handle the error
// the original exception is inside the InnerException property
// the error message explains what happened
}
is simpler than this:
try
{
result = await AwesomeHelpers.ComputeSomethingImportAsync(input);
}
catch(FooApiErrorException exception)
{
// handle Foo API error
}
catch(MissingRecordException exception)
{
// handle missing record error
}
catch(BarApiErrorException exception)
{
// handle Bar API error
}
What do you think about this ? Any thoughts or observation on this ? Do you know any useful reference for this code design issue ?