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>
|