summaryrefslogtreecommitdiffstats
path: root/sandbox/jim/docs/exception_handling.html
blob: 84b075baf0f8e9a7aa0ca986627836ecefcb9d71 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
    <head>
        <title>Exception Handling Guidelines for The Java Runtime</title>
    </head>
    <body>
        <h1>Java Runtime Exception Policy</h1>
        <p> The key tenet of this exception policy is that exceptions should be designed with an eye
            toward what the catch clause would likely do with the exception. The three main cases
            are: </p>
        <ul>
            <li>Code Exception: Code can work around the discovered problem</li>
            <li>User Exception: Problem to be remedied by a human (e.g. Administrator)</li>
            <li>Assertion Exception: Problem remedied by human fixing a bug in the code</li>
        </ul>
        <h2>Code Exceptions</h2>
        <p> These are exceptions where it is expected that some calling code may be able to
            completely handle the exception, without involvement of any user. In other words, the
            exception is of an alternate way of returning a value. There is a reasonable chance that
            calling code (maybe a couple levels up) will be able to catch the exception and either
            try again or try some other approach to accomplishing its job. Note that there may be no
            way of knowing whether the caller will be able to figure out a different approach to
            handling the situation. This is especially true in reusable utility code. In these
            cases, the exceptions should be considered to be code exceptions. The code that handles
            the exception might just turn it into a different kind of exception.</p>
        <h3>Implications</h3>
        <p>In general, code exceptions should be checked exceptions. They should be named based on
            what happened, rather than based on who is throwing the exception. If the exception is
            well named, it should be possible for the exception to be present on signatures at
            several levels of a call stack and still make sense (e.g. ServiceUnavailableException).</p>
        <p>There are some cases where code exceptions should not be checked exceptions. If code
            cannot reasonably be expected to recover from an exception, it should be unchecked,
            Also, iIf a large fraction of the methods in the code would need to declare the
            exception, then its declaration doesn't add much value and so it should be a
            RuntimeException so it doesn't need to be declared. One example of this kind of
            exception might be a RetryException. This exception might occur on some kind of resource
            conflict where retrying the transaction is likely to solve it. Since it is solved
            without human involvement it is still a "code exception". </p>
        <h2>User Exceptions</h2>
        <p> These are exceptions that signal a problem that will be handled by a person, so the most
            important component of the exception is the message, rather than the type of the
            exception. Unfortunately, the code that throws the original exception often will not
            have enough information to give a meaningful message to the user that has all the
            necessary context. The typical "user" in this situation is an administrator, where a
            stack traceback wouldn't be very helpful. Because of this, it is important that code be
            littered with try/catch blocks that do no more than add context to the exception message
            and then rethrow.</p>
        <p>In a previous project this was done by having a base UserException class that had an
            array of messages, rather than just one message. For example, code that parses an SCA
            subsystem file might have a rethrow that just adds "While parsing the xyz subsystem
            file". That is a message that could not be generated by the code that discovered the
            problem (say an XML parsing problem), so a combination of the original message (e.g.
            "Missing end tag") and the higher level message ("while parsing the xyz subsystem file")
            are both necessary for know what happened. Naturally it can be any number of levels
            deep.</p>
        <p> The handling code for a user exception will somehow notify a user of the message and
            then possibly go on. There should be different kinds of exceptions when there need to be
            different ways of handling of the message or different ways to continue. Different ways
            to report the error: In a server, user exceptions can often be divided according to
            fault: </p>
        <ul>
            <li>It's the fault of the client code that is sending the incoming message (e.g. SOAP
                faults).</li>
            <li>It's the fault of the code or configuration that is handling the message. </li>
        </ul>
        <p> If the problem is the fault of the client code, then the message needs to be reported
            back to the client code in a format appropriate for the client. If the problem is the
            fault of the server code or configuration, then only a vague "I've got a problem here"
            message should be sent to the client and the real exception message should be logged
            and/or sent to an administrator. Because of the two different ways of handling the
            problem, there should be different exception types. For example, ClientException could
            be used for exceptions that signal problems that are the client's fault. </p>
        <p> The remaining user exceptions are typically problems with configuration or the
            environment. Some of them will be severe enough that the entire application needs to be
            brought down, while others could be handled by just logging the problem and going on.
            This difference implies that there needs to be a different exception type. Advanced
            Scenario: In the case of session-scoped services, the problem is likely to require that
            the instance of the service be put into an error state (like paused). This is because
            subsequent messages for the service have been sent on the assumption that the previous
            message actually gets processed. If some configuration error prevents a session-scoped
            service from handling a single message, all future (async) messages for that service
            instance should be queued up so they can be processed once the problem has been solved. </p>
        <h2>Assertion Exceptions</h2>
        <p> Assertion exceptions are exceptions that result from a bug in Tuscany and as such are
            also intended to be solved by humans, but in this case the humans are us --are the
            developers of the SCA runtime. In these cases the message isn't nearly as important,
            since the stack traceback provides valuable context. If an assertion exception occurs
            little can be known about the state of the server. If we wanted to be safe we would say
            that assertion exceptions always bring down the entire server. However, we could play it
            a little looser and say that assertion exceptions only bring down the application in
            which they are discovered. </p>


        <h2>Guidelines</h2>
        <p> The following are a set of guidelines based on the above exception philosophy: </p>
        <h4>1. Checked vs. unchecked exceptions</h4>
        <p> Unchecked exceptions should be used when an error condition is not recoverable. Checked
            exceptions thrown by third party libraries that are not recoverable should be wrapped in
            unchecked exceptions rather than being propagated up the call stack. For example, an
            IOException raised when reading a file might be wrapped in an unchecked LoadException
            containing the name of the file.
            Unchecked must always be Javadoced and declared in the throws clause of a method. </p>
        <h4>2. Assertion exceptions should use the standard JDK assert facilities</h4>
        <h4>3. Any exception thrown to user code must extend the appropriate Exception as defined
               by the specification. This will typically be a runtime Exception.</h4>
        <h4>4. No other Exceptions should be thrown to user code. Each user API method should
               catch any internal exceptions and wrap them in the applicable Exception defined
               by the specification. Internal exceptions must ultimately extend either TuscanyException
               or TuscanyRuntimeException.
        <h4>4. When possible, create clear package exception hierarchies</h4>
        <p> In most cases, packages should have a clear exception hierarchy with abstract root
            checked and unchecked exceptions which more specific concrete exceptions extend.
            Declaring the root package exceptions abstract avoids code throwing exceptions which are
            too general. Creating an exception hierarchy allows client code using a particular
            package to choose the level of exception handling granularity (which in turn simplifies
            the client code by avoiding unwieldy try..catch clauses). </p>
        <h4> 5. Preserve all stack trace information and the original exception</h4>
        <p> Exceptions must always preserve the stack trace and original exception except under
            special circumstances. When wrapping exceptions to propagate, never modify the stack
            trace and always include the caught exception as the cause.</p>
        <h4>6. Only include local information pertinent to the failure</h4>
        <p> For I18N, contextual information stored in the Exception should not be localized. It
            should comprise only data pertaining to the cause, such as the name of the artifact as
            above, or a key that can be used by the top level exception handler. This is needed
            because the locale used to render the exception may be completely different from the
            locale used by the code raising the exception. For example, an exception may be thrown
            on a system whose default locale is German, logged to the system log in English but
            displayed to the end user in French, Japanese, whatever their native language is. </p>
        <h4>7. For exceptions that require contextual information from various code layers, either
            wrap exceptions or create exceptions that can accept additional context as they are
            propagated up the call stack.</h4>
        <p> If a failure requires information from multiple levels, e.g. “there was an error setting
            property X on component Y in module Z” do one of the following. If the initial exception
            should be wrapped as it is propagated (e.g. the exception occurs at a library boundary),
            add additional context information in the wrapping exception(s). If the initial
            exception can be propagated, include methods for adding additional context information
            as the exception is rethrown up the stack. For example, the previous failure scenario
            could result in the following exception handling strategy: </p>
        <ul>
            <li> A component property is configured with an invalid integer type</li>
            <li> The property value parsing code attempts to load an integer value using parseInt(),
                resulting in a NumberFormatException</li>
            <li> NumberFormatException is wrapped in an InvalidParameterException (IPE) containing
                the name of the property.</li>
            <li> IPE extends a more general ConfigException, which has setters for adding additional
                context information such as component and module names</li>
            <li> As the IPE is thrown up the stack, the component and module parsers provide
                additional context information.</li>
            <li> The configuration loader then wraps the IPE in a ConfigLoadExeption and provides
                the source from which the configuration is being loaded.</li>
            <li> The UI being used to load the configuration reports the error to the user and
                displays the appropriate contextual information</li>
        </ul>
        <h4>8. getMessage() must return unformatted context info. If the Exception contains multiple
               context fields they should be surrounded in square brackets and separated by commas,
               e.g. "[ property X, component Y, module Z ]"</h4>
        <h4>9. Do not override the behaviour of Throwable.toString() and Throwable.printStackTrace()</h4>
        <h4>10. The java.lang.Exception base class is Serializable so all subclasses must provide
                a serial UID. Any context fields must be Serializable and should be defined in the
                base java namespace for JDK1.4.</h4>
        <h4>11. Exceptions that wrap other Exceptions should ensure that any wrapped Exception can
                be deserialized in a client environment. This may require providing a custom
                writeObject method to extract any context information from the wrapped Exception
                during serialization; at a minimum the message should be preserved.</h4>
    </body>
</html>