1

I am building a .NET client library for a public web API. One of the things I'm struggling with is how best to indicate error conditions to a user of the library. Obviously my library should throw or pass exceptions to the caller when there's an execution failure, but should I use existing exceptions or create new exceptions for my library? If I create new exceptions, should I create just one exception or multiple?

Option 1: Use existing exceptions exclusively

At its most basic, I could just for example throw new Exception("API authentication error."); but this is very generic and would require a lot of processing to deal with afterwards. I could also use more appropriate existing exception like System.Security.Authentication.AuthenticationException, but then using the library will be awkward because of all the different namespaces. There will be some cases where there is nothing more appropriate than InvalidOprationException, so it seems that taking this approach will make it cumbersome for whoever is using the library to process errors. The simplest would probably be to just use the HttpException class, since most error conditions are in any case a translation of HTTP status codes and messages.

Option 2: Create an exception for each situation

I could also create an exception for each scenario, but keep them all within the library. So I could create a NotFoundException, AuthorizationException, TransactionsPendingException and other specific exceptions.

Option 3: Create a single exception for my client

The third option would be to create a single custom exception MyApiException which contains a status code for the different errors that come up (and an appropriate message, of course). So I might then throw new MyApiException(401,"Not authorized.")

It seems to me that option 3 is the most straightforward to implement and easiest for the calling method to deal with. I have seen API clients on GitHub and Codeplex which use each of these options. Is there a recommended approach for client libraries or are all of them valid?

rudivonstaden
  • 7,675
  • 5
  • 26
  • 41

1 Answers1

2

Microsoft has some valuable information about this over on MSDN:

https://msdn.microsoft.com/en-us/library/seyhszts%28v=vs.110%29.aspx

Personally, I tend to do the following:

  1. Use existing exceptions when it makes sense

    e.g. a key-based op throwing KeyNotFoundException when passed an invalid key

  2. Create custom exceptions for scenarios that warrant it

  3. Eschew exceptions in favour of a result type like Either<TSuccess,TFail>
    1. An example of this is here: https://gist.github.com/siliconbrain/3923828
    2. This is particularly useful for things like iterator blocks where throwing exceptions could be problematic, or when you need to support continue-on-error

Existing Exceptions

public int GetMyItem(string key)
{
   if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");
   if (!_myDictionary.ContainsKey(key))
   {
      throw new KeyNotFoundException();
   }
   // Additional implementation left out
}

Custom Exceptions

public class ArgumentNotOddException : ArgumentException
{
    public ArgumentNotOddException(string paramName)
        : base(@"The argument must be odd", paramName)
    {

    }
}

public class MyClass
{
    public void DoSomething(int x)
    {
        if (x%3 == 0) throw new ArgumentNotOddException("x");
    }
}

Either Type

public class MyClass
{
    public IEnumerable<IEither<int, string>> Divide(int left, IEnumerable<int> right)
    {
        foreach (var num in right)
        {
            if (num == 0)
            {
                yield return Either.Right<int, string>("cannot divide by zero");
            }
            yield return Either.Left<int, string>(left/num);
        }
    }
}

You could also just create a MyAppException that uses an exception code and message, but then you lose the expressiveness, exception codes are prone to mis-typing (unless you use an enum), and MyAppException doesn't say quite the same thing as NotFoundException.

My best advice would be to think hard about whether the code should throw, or whether it's reasonable for it to return a hybrid result / error instead; it's a little clunky to do so in C#, but it removes the burden of unhandled exceptions, and allows you to be more flexible in your calling code.

Clint
  • 6,133
  • 2
  • 27
  • 48
  • Thanks @Clint, there is some useful advice there. My reasoning for having a single exception for the app is that it makes it easier for the calling code to catch and process. You could just use `catch (MyApiException ex)` and handle the result appropriately based on the status code. This would be equivalent to dealing with the fail side of the `Either` result type but easier to implement? – rudivonstaden Apr 08 '15 at 13:24
  • @rudivonstaden possibly, but what if a method returns multiple error codes? Then you have to handle that with if / switch statements after catching it, and what happens if it spits out a code you're not expecting? – Clint Apr 08 '15 at 13:25
  • @rudivonstaden really, that indicates a method should be smaller and only do a simple pass/fail type result, there's no one-size-fits-all answer here, but I like the either / maybe approach as it follows the patterns set out by most functional languages (e.g. f#), you can still throw exceptions, but the best approach is to treat failure as a part of the program flow. – Clint Apr 08 '15 at 13:27
  • 1
    there would only be one error code. Presumably the first error it encountered which prevents the successful processing of the request. If it spits out an unexpected code you've still caught it with `try..catch`. You can by default assume that the operation did not work and respond accordingly. The reason why I'm querying the Either approach is that Microsoft recommends against returning error codes in the best practices page you linked to. I'm still wrapping my head around the pros and cons. – rudivonstaden Apr 08 '15 at 13:40
  • 1
    @rudivonstaden welcome to the wonderful world of error handling! The best practices are pretty old now, and I agree with exceptions most of the time, it's just that sometimes there's a difference between an app error (exception) and a recoverable error in processing that could result in an error code, or some default value. It really is down to a per-case basis. – Clint Apr 08 '15 at 13:42
  • ***error handling*** with good patterns for *Minimal apis NET5-NET6*? – Kiquenet Oct 27 '22 at 19:54