How do I recover from an unchecked exception?

2020-06-22 01:38发布

Unchecked exceptions are alright if you want to handle every failure the same way, for example by logging it and skipping to the next request, displaying a message to the user and handling the next event, etc. If this is my use case, all I have to do is catch some general exception type at a high level in my system, and handle everything the same way.

But I want to recover from specific problems, and I'm not sure the best way to approach it with unchecked exceptions. Here is a concrete example.

Suppose I have a web application, built using Struts2 and Hibernate. If an exception bubbles up to my "action", I log it, and display a pretty apology to the user. But one of the functions of my web application is creating new user accounts, that require a unique user name. If a user picks a name that already exists, Hibernate throws an org.hibernate.exception.ConstraintViolationException (an unchecked exception) down in the guts of my system. I'd really like to recover from this particular problem by asking the user to choose another user name, rather than giving them the same "we logged your problem but for now you're hosed" message.

Here are a few points to consider:

  1. There a lot of people creating accounts simultaneously. I don't want to lock the whole user table between a "SELECT" to see if the name exists and an "INSERT" if it doesn't. In the case of relational databases, there might be some tricks to work around this, but what I'm really interested in is the general case where pre-checking for an exception won't work because of a fundamental race condition. Same thing could apply to looking for a file on the file system, etc.
  2. Given my CTO's propensity for drive-by management induced by reading technology columns in "Inc.", I need a layer of indirection around the persistence mechanism so that I can throw out Hibernate and use Kodo, or whatever, without changing anything except the lowest layer of persistence code. As a matter of fact, there are several such layers of abstraction in my system. How can I prevent them from leaking in spite of unchecked exceptions?
  3. One of the declaimed weaknesses of checked exceptions is having to "handle" them in every call on the stack—either by declaring that a calling method throws them, or by catching them and handling them. Handling them often means wrapping them in another checked exception of a type appropriate to the level of abstraction. So, for example, in checked-exception land, a file-system–based implementation of my UserRegistry might catch IOException, while a database implementation would catch SQLException, but both would throw a UserNotFoundException that hides the underlying implementation. How do I take advantage of unchecked exceptions, sparing myself of the burden of this wrapping at each layer, without leaking implementation details?

9条回答
疯言疯语
2楼-- · 2020-06-22 02:26
  1. The question is not really related to checked vs. unchecked debate, the same applies to both exception types.

  2. Between the point where the ConstraintViolationException is thrown and the point, where we want to handle the violation by displaying a nice error message is a large number of method calls on the stack that should abort immediately and shouldn't care about the problem. That makes the exception mechanism the right choice as opposed to redesigning the code from exceptions to return values.

  3. In fact, using an unchecked exception instead of a checked exception is a natural fit, since we really want all intermediate methods on the call stack to ignore the exception and not handle it .

  4. If we want to handle the "unique name violation" only by displaying a nice error message (error page) to the user, there's not really a need for a specific DuplicateUsernameException. This will keep the number of exception classes low. Instead, we can create a MessageException that can be reused in many similar scenarios.

    As soon as possible we catch the ConstraintViolationException and convert it to a MessageException with a nice message. It's important to convert it soon, when we can be sure, it's really the "unique user name constraint" that was violated and not some other constraint.

    Somewhere close to the top level handler, just handle the MessageException in a different way. Instead of "we logged your problem but for now you're hosed" simply display the message contained in the MessageException, no stack trace.

    The MessageException can take some additional constructor parameters, such as a detailed explanation of the problem, available next action (cancel, go to a different page), icon (error, warning)...

The code may look like this

// insert the user
try {
   hibernateSession.save(user);
} catch (ConstraintViolationException e) {
   throw new MessageException("Username " + user.getName() + " already exists. Please choose a different name.");
}

In a totally different place there's a top exception handler

try {
   ... render the page
} catch (MessageException e) {
   ... render a nice page with the message
} catch (Exception e) {
   ... render "we logged your problem but for now you're hosed" message
}
查看更多
老娘就宠你
3楼-- · 2020-06-22 02:26

@erikson

Just to add food to your thoughts:

Checked versus unchecked is also debated here

The usage of unchecked exceptions is compliant with the fact they are used IMO for exception caused by the caller of the function (and the caller can be several layers above that function, hence the necessity for other frames to ignore the exception)

Regarding your specific issue, you should catch the unchecked exception at high level, and encapsulate it, as said by @Kanook in your own exception, without displaying the callstack (as mentionned by @Jan Soltis )

That being said, if the underlying technology changes, that will indeed have an impact on those catch() already present in your code, and that does not answer your latest scenario.

查看更多
smile是对你的礼貌
4楼-- · 2020-06-22 02:28

I agree with Nick. Exception you described is not really "unexpected exception" so you should design you code accordingly taking possible exceptions into account.

Also I would recommend to take a look at documentation of Microsoft Enterprise Library Exception Handling Block it has a nice outline of error handling patterns.

查看更多
登录 后发表回答