9

I've run into a debate between a few friends and I. They prefer general exceptions such as ClientErrorException and ServerErrorException with detail as fields of the exception, whereas I prefer making things more specific. For example, I might have a handful of exceptions like:

  • BadRequestException
  • AuthenticationFailureException
  • ProductNotFoundException

Each of these built based on the error code returned from the API.

Following Advantages of Exceptions this seems idiomatic to Java. However, my friends' opinion is not exactly uncommon.

Is there a preferred way in terms of code readability and API usability, or does it really just come down to preference?

  • The page you linked is likely the best definitive answer we can get. You are asking for an opinion, really. I can answer with what my experience and opinion is, but that's not an objective answer. – marstato May 12 '18 at 5:59
  • @marstato that's fair. I guess I'm sort of looking for justification in my position. I'd rather keep to what people expect in the libraries I write, rather than following a guide if it means it makes my stuff easier to use, you know? – user304989 May 12 '18 at 6:13
  • I absolutely agree. I have my exception classes granular, too. Also, you can define abstract and generalized exception classes with the getter methods and then make the granular ones extend the general ones. E.g. AuthenticationFaliureException extends ClientErrorException. This way, every user can choose how they'd like to deal with the exceptions. It more work though, obviously. However, when writing an application (instead of a library), it's a different situation IMHO. In that case I'd not make the exceptions more granular than you need them, for simplicities sake. – marstato May 12 '18 at 6:30
  • @marstato that is actually how I implement it now. I'm glad you agree. I'm gonna leave the question open over night, but please consolidate that into a post so I can at least green check you – user304989 May 12 '18 at 6:33
15

The main difference between having many different exception classes, and having only a few, with more detailed information in the error text (for example), is that many different exception classes allow the calling code to react differently to different kinds of errors, whilst having only a few classes makes it easier to handle all kind of exceptions in a uniform way.

This is typically a tradeoff. It can be mitigated to some degree by using inheritance (a general base exception class for those callers who want to catch & log everything generically, and derived exceptions from those base class for those callers who need different reactions), but even that can produce a lot of unnecessary complexity if you don't be careful and stick to the YAGNI principle. So the guiding question should be here:

  • Do you really expect the caller of your code to react differently, with different flow of control, to those different kind of errors?

There is no one-size-fits-all solution to this, no braindead "best practice" you can apply each and everywhere. The answer to this question is heavily dependent on what kind of software or component you are designing:

  • some application, where you or the team has the whole code base under your control?

  • or some reusable component for third parties, where you don't know all the potential callers?

  • a long running server application, where different kind of errors should not break the whole system immediately and may require different kinds of error mitigation?

  • a short-living application process where it is enough in case of an error to display an error message to the user and then restart the process?

So the more you know about the potential callers of your component, the better you can decide about the correct level of detail for your exeptions.

  • 3
    "Do you really expect the caller of your code to react differently, with different flow of control, to those different kind of errors?" this is a great question to ask. Thank you, I'm going to remember this. – user304989 May 12 '18 at 8:08
  • This is a good answer I think, I would only further emphasize further the need to let the client application drive the exception throwing design. Even the generalizations you might think sensible to build in to the exception hierarchy might not match the ways in which your client application (if it is not yourself writing it) might choose to generalize a handler. If you have more than one client application with different needs, then of course it will be up to you to balance them. – magicduncan May 12 '18 at 20:07
1

The answer depends on what level of error reporting we are talking about.

In general I agree with your friends, you should not make them more granular than needed to convey the cause of the problem.

If there is a common, well known exception for referencing null (NullReferenceException), you should not create your own MyObjectIsNullException. That would just add a layer of confusion for the human interpreter, an additional thing to learn that does not clarify anything.

Only when your exception is that special that there is no predefined one that covers the root cause should you create your own.

However, you do not have to stop there. The common error may occur in one of your components and you may want to convey there was a problem in your component. So not just what went wrong but also where. Then it would be appropriate to wrap the first exception in a MyComponentException. That will give you best of both worlds.

First it will be clear your component ran into trouble. On a lower lever, the specific cause would be in the inner exception.

  • That's fair. So you're suggesting don't invent a new exception when a good one already exists? – user304989 May 12 '18 at 7:19
  • @rec Yes, because everyone already knows the predefined ones. That is what they are there for. – Martin Maat May 12 '18 at 7:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy