11

I'm struggling with a very simple question:

I'm now working on a server application, and I need to invent a hierarchy for the exceptions (some exceptions already exist, but a general framework is needed). How do I even start doing this?

I'm thinking of following this strategy:

1) What is going wrong?

  • Something is asked, which is not allowed.
  • Something is asked, it is allowed, but it does not work, due to wrong parameters.
  • Something is asked, it is allowed, but it does not work, because of internal errors.

2) Who is launching the request?

  • The client application
  • Another server application

3) Message handing : as we are dealing with a server application, it's all about receiving and sending messages. So what if the sending of a message goes wrong?

As such, we might get following exception types:

  • ServerNotAllowedException
  • ClientNotAllowedException
  • ServerParameterException
  • ClientParameterException
  • InternalException (in case the server does not know where the request is coming from)
    • ServerInternalException
    • ClientInternalException
  • MessageHandlingException

This is a very general approach to define exception hierarchy, but I'm afraid that I might be lacking some obvious cases. Do you have ideas on which areas I'm not covering, are you aware of any drawbacks of this method or is there a more general approach to this kind of question (in the latter case, where can I find it)?

Thanks in advance

8
  • 5
    You did not mention what you want to achieve with your exception class hierarchy (and that's not at all obvious). Meaningful logging? Enabling clients to react reasonably to the different exceptions? Or what? Jan 26, 2018 at 10:24
  • 2
    It's probably useful to work through some stories and use cases first, and see what comes out. For example: client requests X, is not permitted. Client requests X, but the request is invalid. Work through them thinking about who should handle the exception, what they can do with it (prompt, retry, whatever), and what information they need to do that well. Then, once you know what your concrete exceptions are and what information the handlers need to process them, you can form them into a nice hierarchy.
    – Useless
    Jan 26, 2018 at 12:02
  • 1
    Of relevance, I'd guess: softwareengineering.stackexchange.com/questions/278949/…
    – Martin Ba
    Jan 26, 2018 at 14:49
  • 1
    I've never really understood the desire to want to use so many different exception types. Typically for most catch blocks I use, I don't have much more use for the exception than what error message it contains. I don't really have anything different I can do for an exception involved in failing to read a file as one failing to allocate memory during the process of reading it, so I tend to just catch std::exception and report the error message it contains, maybe decorating it with "Failed to open file: %s", ex.what() to a stack buffer before printing it.
    – user204677
    Jan 27, 2018 at 11:15
  • 3
    Besides I can't anticipate all the exception types that will be thrown in the first place. I might be able to anticipate them now, but colleagues may introduce new ones in the future, e.g. So it's kind of hopeless for me to know, for now and forever, all the different types of exceptions that could be thrown in the process of an operation. So I just catch super generally with one single catch block. I have seen examples of people using many different catch blocks in a single recovery site, but often it's just to ignore the message inside the exception and print a more localized message...
    – user204677
    Jan 27, 2018 at 11:17

5 Answers 5

6

General remarks

(a bit opinion-biased)

I'd typically not go for a detailed exception hierarchy.

Most important thing: an exception tells your caller that your method failed to complete its job. And your caller must get notice about that, so he doesn't simply continue. That works with any exception, no matter what exception class you choose.

Second aspect is logging. You want to find meaningful log entries whenever something goes wrong. That also doesn't need different exception classes, only well-designed text messages (I suppose you don't need an automat to read your error logs...).

Third aspect is reaction of your caller. What can your caller do when he receives an exception? Here it can make sense to have different exception classes, so the caller can decide whether to retry the same call, to use a different solution (e.g. use a fallback source instead), or to give up.

And maybe you want to use your exceptions as the base for informing the end user about the problem. That means creating a user-friendly message besides the admin-text for the log file, but doesn't need different exception classes (although maybe that can make the text generation easier...).

An important aspect for logging (and for user error messages) is the ability to amend the exception with context information by catching it at some layer, adding some context information, e.g. method parameters, and re-throwing it.

Your hierarchy

Who is launching the request? I don't think you'll need the information who was launching the request. I can't even imagine how you know that deep inside some call stack.

Message handling: that's not a different aspect, but just additional cases for "What is going wrong?".

In a comment, you talk about a "no logging" flag when creating an exception. I don't think that at the place where you create and throw an exception, you can make a reliable decision whether or not to log that exception.

The only situation I can imagine is that some higher layer uses your API in a way that will sometimes produce exceptions, and this layer then knows that it need not bother any administrator with the exception, so it silently swallows the exception. But that's a code smell: an expected exception is a contradiction in itself, a hint to change the API. And it's the higher layer that should decide, not the exception-generating code.

2
  • In my experience, an error code in combination with a user friendly text works very well. Admins can use the error code to find additional information.
    – Sjoerd
    Jan 26, 2018 at 11:40
  • 1
    I like the idea behind this answer in general. From my experience Exceptions really shouldn't ever turn into too complex of a beast. The main purpose of an exception is to allow the calling code to address a specific issue and get the relevant debugging/retry information without messing with the response of the function.
    – greggle138
    Jan 26, 2018 at 15:38
3

The main thing to keep in mind when designing an error response pattern is to make sure it's useful to callers. This applies whether you're using exceptions or defined error codes, but we'll restrict ourselves to illustrating with exceptions.

  • If your language or framework already provides general exception classes, use them where they are appropriate and where they are to be reasonably expected. Don't define your own ArgumentNullException or ArgumentOutOfRange exception classes. Callers won't be expecting to catch those.

  • Define a MyClientServerAppException base class to encompass the errors that are unique within the context of your application. Never throw an instance of the base class. An ambiguous error response is THE WORST THING EVER. If there is an "internal error", then explain what that error is.

  • For the most part, the hierarchy beneath the base class should be broad, not deep. You only need to deepen it in situations where it is useful to the caller. For example, if there are 5 reasons a message might fail from client to server, you could define a ServerMessageFault exception, then define an exception class for each of those 5 errors beneath that. That way, the caller can just catch the superclass if it needs or wants to. Try to limit this to specific, reasonable cases.

  • Don't try to define all of your exception classes before they are actually used. You'll wind up re-doing most of it. When you encounter an error case while writing code, then decide how best to describe that error. Ideally, it should expressed in the context of what the caller is trying to do.

  • Related to the previous point, remember that just because you use exceptions to respond to errors, that doesn't mean that you have to use only exceptions for error states. Bear in mind that throwing exceptions is ususally expensive, and the performance cost can vary from one language to another. With some languages, the cost is higher depending on the depth of the call stack, so if an error is deep within a call stack, check to see if you can't use simple primitive types (integer error codes or boolean flags) to push the error back up the stack, so it can be thrown closer to the caller's invocation.

  • If you're including logging as part of the error response, it should be fall-down easy for callers to append context information to an exception object. Start from where the information is used in the logging code. Decide how much information is needed for the log to be useful (without being a giant wall of text). Then work backwards to make sure the exception classes can be easily provided with that information.

Finally, unless your application can absolutely deal gracefully with an out-of-memory error, don't try to deal with those, or other catastrophic runtime exceptions. Just let the OS deal with it, because in reality, that's all you can do.

2
  • 2
    Except for your second to last bullet (about the cost of exceptions), the answer is good. That bullet about the cost of exceptions is misleading, because in all common ways of implementing exceptions at the low level, the cost of throwing an exception is completely independent of the depth of the call stack. Alternative error-reporting methods might be better if you know that the error will be handled by the immediate caller, but not to get a few functions off the call stack before throwing an exception. Jan 28, 2018 at 9:20
  • @BartvanIngenSchenau: I tried not to tie this to a specific language. In some languages (Java, for example), the depth of the call stack affects the cost of instantiation. I'll edit to reflect that it isn't so cut and dried. Jan 28, 2018 at 15:54
1

Exceptions are unrestricted gotos and must be used with care. The best strategy for them is to restrict them. The calling function must handle all the exceptions thrown by the functions it calls or the program dies. Only the calling function has the correct context for handling the exception. Having functions further up the call tree handle them are unrestricted gotos.

Exceptions are not errors. They occur when circumstances prevent the program from completing one branch of the code and indicate another branch for it to follow.

Exceptions must be in the context of the called function. For example: a function that solve quadratic equations. It must handle two exceptions: division_by_zero and square_root_of_negative_number. But these exceptions do not make sense for functions calling this quadratic-equation solver. They happen because of the method used to solve equations and simply rethrowing them exposes the internals and breaks encapsulation. Instead, division_by_zero should be rethrown as not_quadratic and square_root_of_negative_number and no_real_roots.

There is no need for a hierarchy of exceptions. An enum of the exceptions (which identifies them) thrown by a function is sufficient because the calling function must handle them. Allowing them to process up the call tree is an out-of-context (unrestricted) goto.

0

Well I would recommend you first and foremost create a base Exception class for all checked exceptions which may be thrown by your application. If your application were called DuckType, then make a DuckTypeException base class.

This lets you catch any exception of your base DuckTypeException class for handling. From here, your exceptions should branch off with descriptive names which can better highlight the type of problem. For instance, "DatabaseConnectionException".

Let me be clear, these should all be checked exceptions for situations that could happen that you would likely want to handle gracefully in your program. In other words, you cannot connect to the database, so a DatabaseConnectionException is thrown which you can then catch to wait and retry after a period of time.

You would not see a checked exception for a very unexpected problem such as invalid SQL query or a null pointer exception, and I would encourage you to let these exceptions transcend most catch clauses (or caught and rethrown as necessary) until you arrive at the main controller itself, which can then catch RuntimeException's purely for logging purposes.

My personal preference is to not rethrow an unchecked RuntimeException as another exception, as by the nature of an unchecked exception, you wouldn't expect it and therefore rethrowing it under another exception is hiding information. However, if that is your preference, you can still catch a RuntimeException and throw a DuckTypeInternalException which unlike DuckTypeException derives from RuntimeException and is therefore unchecked.

If you prefer, you can subcategorize your exceptions for organizational purposes such as DatabaseException for anything database-related, but I would still encourage such sub-exceptions to derive from your base exception DuckTypeException and to be abstract and therefore derived with explicit descriptive names.

As a general rule, your try catches should be increasingly generic as you move up the callstack of callers for handling exceptions, and again, in your main controller, you would not handle a DatabaseConnectionException but rather the simple DuckTypeException of which all of your checked exceptions derive.

1
  • 2
    Note that the question is tagged "C++".
    – Martin Ba
    Jan 26, 2018 at 14:50
0

Try to simplify that.

The first thing that will help you to think in another strategy is: caught many exceptions is very similar to the use of checked exceptions from Java (sorry, I'm not C++ developer). This is not good for many reasons so I always try to not use them, and your hierarchy exception strategy remembers me a lot of that.

So, I recommend you another and flexible strategy: use unchecked exceptions and code errors.

Per example, see this Java code:

public class SystemErrorCode implements ErrorCode {

    INVALID_NAME(101),
    ORDER_NOT_FOUND(102),
    PARAMETER_NOT_FOUND(103),
    VALUE_TOO_SHORT(104);

    private final int number;

    private ErrorCode(int number) {
        this.number = number;
    }

    @Override
    public int getNumber() {
        return number;
    }
}

And your unique exception:

public class SystemException extends RuntimeException {

    private ErrorCode errorCode;

    public SystemException(ErrorCode errorCode) {
        this.errorCode = errorCode;
    }

}

This strategy I found on this link and you can find the Java implementation here, where you can see more details about it, because the code above is simplified.

As you need to separate the different exceptions among "client" and "another server" applications, you can have multiple error code classes implementing the interface ErrorCode.

2
  • 2
    Note that the question is tagged "C++".
    – Sjoerd
    Jan 26, 2018 at 11:43
  • I edited to adapt the idea.
    – Dherik
    Jan 31, 2018 at 15:26

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.