2

Imagine this simple application use case, say, for fetching data from outisde of the app. These steps represent the "depth" of the layers, from top to bottom.

  1. UI touch event
  2. ViewModel handles said event
  3. Some service / manager reacts accordingly
  4. The correct request is built and executed
  5. The object is fetched and returned all the way up.

This is a simple example to make my point, we're not trying to be super-precise here.

What matters is : I'm digging in the layers, and an exception occurs.

The higher I am in the stack, the better I know the context of the error. I know it happened when I hit the "login" button or the "download" button. So I'm thinking I have a lot of information on context.

The deeper I am in the stack, the closer I get to the actual cause of the exception. I know exactly why it happened at this stage. I have a lot of information about the actual error itself. But I don't really want to show it as is to the user (if relevant).

(Note : we're supposing the exception happened at layer 5 or 4 ; the question isn't that relevant if it happens in the UI layer).

As a general rule of thumb, I try to have very specific error messages according to the context rather than the error message itself ; the same exception type could display many different messages depending on where it came from in the UI. I want more precision as I go up the stack.

My question is :

What exception object/design should I use ? I'm confused, should I have very specific exceptions the deeper I get, because I know specific details ? Or on the other hand, should I use very broad exceptions and transform it while it's caught going up the layers, because I understand context better as I move up?

I can't really figure out how I can get both precise/nice messages without having tons of specific exception catching too low in the architecture. Correct me if I'm wrong, but my Requestshould not have many caught exception types, since the request is not aware of the current context ; yet I need them if I want to be more accurate in my message.

For example, I'll probably use the same Request class for multiple different calls, so it would be a RequestException or a ServerException ; but when the user lives it, he might be doing two very different things.

Again, I'm just making examples up for the sake of the example, but a login and a data-fetching should be handled totally differently, yet, at the bottom, they could be both the same ServerException, which is way too broad if I want to display something specific to the UI

I'm not sure how to handle this, it seems tricky/confusing, yet it should be very simple since I appear to have all the information I should have. What am I missing?

And more importantly, how do you guys handle your exceptions, usually, when going up the stack?

2 Answers 2

4

The higher I am in the stack, the better I know the context of the error. I know it happened when I hit the "login" button or the "download" button. So I'm thinking I have a lot of information on context.

The deeper I am in the stack, the closer I get to the actual cause of the exception. I know exactly why it happened at this stage. I have a lot of information about the actual error itself. But I don't really want to show it as is to the user (if relevant).

The way out of this conundrum is to recognize that exceptions are meant to be consumed by software and not your end-users.

When the lower layers encounter a problem, they should throw an exception that contains as much information about the actual problem as is available at that point. This information is probably completely useless to your end-user, but it will be valuable to your developers (if it ends up in a log file).

In the UI layer, you should catch all exceptions and show an error dialog to the user based on both the context of what the user is doing and relevant information contained in the exception.
The exception itself could be logged in a log-file, but should certainly not be dumped on your end-user.

3

UI touch event

ViewModel handles said event

Some service / manager reacts accordingly

The correct request is built and executed

The object is fetched and returned all the way up.

You should chain your exceptions.

Each exception block catches exceptions caught from lower in, it should explain the context that the exception occurred and throw up to higher level.

So the initial exception at the lowest level might be something like that an external service is not available. The code that talks to this API throw an exception. A specific exception saying just that the external API is unreachable.

That exception is caught higher up by what ever was initiating the call to the external API service. That catch-throw block throws its own exception, but inside that exception is the original exception.

Higher up still the code that kicked off that action catches and throws etc

Each level should have implement catch-throw, it catches exceptions from below it, but it doesn't do anything with them other than to pack them up in its own exception and throw higher up.

You will end up with an exception that contains all the other exceptions, something like this (say you are reimplementing Google Translate)

TranslationError("Translation could not be completed")
  LanguageIdentificationError("Unable to determine the initial language")
    LanguageServiceError("The Language Service is unavailable")
      HTTPError("Call to service failed, error 500")

Some where at the very top of your application, this exception will get caught. Now obviously you don't want to output this to the user. A simple "Translation failed" message is fine.

But if you now log this exception chain developers maintain and debugging your system will give you a really big hug.

They can look at that exception chain and you can see what happened, the translation failed because the system was unable to determine the language of the request because the language service is unavailable because when the system tried to access it a 500 was returned.

This is much better than getting a HTTPError("Call to service failed, error 500") error at the top of your system and then having to figure out the chain of events that lead to that call.

And it is also much better than having to pass contexts into each layer just so the low level exception knows the over all system context of what it is trying to do. The HTTPError exception should never say something like "Call to service failed, error 500, and you were trying to determine the language of the system using the language service"

1
  • 1
    I know i'm 3 years too late, but thank you for your answer :) I've been reading my older questions to see where I'm at compared to earlier in my life, and answers like yours really helped grow me into a better developer. Thanks again, and have a good day !
    – Gil Sand
    Feb 5, 2019 at 9:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.