Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
If you’re writing a library for other developers to consume on NuGet or if you’re creating an API within your own project, stop throwing exceptions and start being explicit.
Exceptions are like landmines. Consuming callers have absolutely no idea what exceptions you might throw or how/when/why they will step on one. C# is dishonest when it comes to looking at method signatures.
Check out my YouTube channel where I created a video that accompanies this blog post.
I first heard of the concept of honesty in programming languages from a video of Erik Meijer discussing functional programming.
Take for example the following C# interface with a single method:
If you’re using this interface and call GetShippingAddress, what’s your expectation? You should expect to get back a string.
What’s not explicit is that you may also get back null or have an exception thrown which you need to catch. Which type of Exception? Who knows.
You could argue with C# 8 Nullable Reference Types that null is now explicit, so thankfully that’s a non-issue. However, GetShippingAddress throwing an exception is possible and as a consumer, you have no idea that you need to handle exceptions (or which types).
Pit of Failure
The argument I’ve received to this is to use XMLDocs to document your API so that developers/consumers know which exceptions you might throw and why.
That’s very nice of you and XMLDocs are great and should definitely be used. However, it does not force the developer/consumer to catch the relevant exceptions you might throw. This isn’t putting your consumer in the pit of success.
It’s putting a landmine in your API and hoping they know how to deal with it when they step on it.
Pit of Success
I’d always rather be pushed by a library, framework, or programming language into the pit of success.
Here’s an example of an interface and implementation.
If the quantity is less than 0, we’re throwing an exception. Otherwise we return an int, which is the quantity of the product we have in our basket.
The alternative to try/catch is to be explicit in signature by telling our consumer that we are either going to return them a string or a validation error, which is our exception.
To do this, we can create a new type called Either.
Either will contain only one value. Either one of the type parameters of TLeft or TRight.
The Match() method forces the consumer to handle both possible cases. This is the key. It forces the consumer down the path of handling multiple outcomes.
Now let’s remove the exception and instead return a Either<int, QuantityValidationError>
As mentioned the consumer is forced to handle both success and failure. The consumer is forced. It’s a not a choice.
What that looks like in a Razor page.
If we get the quantity back, we’re returning a redirect. If there is a validationError, we are returning a BadRequestResult, both of which are an IActionResult.
Stop Throwing Exceptions
Hopefully, you can see the value in having your APIs guide your consumers and have them be totally honest. There are no surprises that they need to be aware of. What the method signature is, is what they can expect.
If you’ve got any feedback about this approach, let me know in the comments or Twitter.