Skip to content

Commit 2aff8fa

Browse files
authored
Prepare release 0.16.0
Prepare release 0.16.0
2 parents f1d1c38 + e30464c commit 2aff8fa

File tree

80 files changed

+3322
-969
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+3322
-969
lines changed

ExceptionHandling.md

+321
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
# Exception handling
2+
3+
## Common Anti-Pattern
4+
5+
### Diaper (swallowing exceptions)
6+
7+
* Makes the needle disappear from the haystack.
8+
* Debugging is impossible (or very hard at least)
9+
10+
```groovy
11+
try {
12+
doSomeWork();
13+
} catch (Exception e) {
14+
}
15+
```
16+
17+
```groovy
18+
try {
19+
doSomeWork();
20+
} catch (Exception e) {
21+
e.printStackTrace();
22+
}
23+
```
24+
25+
The `printStackTrace()` method prints to `System.err`. Often, **`System.err` is not captured by the
26+
logs.**
27+
28+
### Decapitation
29+
30+
Decapitated exceptions provide less information about the systems state producing the exception.
31+
Never do it.
32+
33+
```groovy
34+
try {
35+
doSomeWork();
36+
} catch (Exception e) {
37+
throw new RuntimeException("my crazy method threw an exception")
38+
}
39+
```
40+
41+
#### Instead do:
42+
43+
```groovy
44+
try {
45+
doSomeWork();
46+
} catch (Exception e) {
47+
throw new RuntimeException("my crazy method threw an exception", e)
48+
}
49+
```
50+
51+
### Checked Exceptions
52+
53+
* Forces caller to catch the exception.
54+
* Caller might have no way to handle it
55+
* Presents an abstraction leak as the caller needs to deal with internals of the implementation.
56+
57+
Why should the calling code know details about the implementation?
58+
59+
<!-- we use groogy as languate so the formatting works ;D -->
60+
61+
```groovy
62+
public static int countUserPosts() throws IOException {
63+
Properties userProps = new Properties();
64+
try (FileReader reader = new FileReader("my-users.properties")) {
65+
userProps.load(reader);
66+
}
67+
return userProps.getProperty("user.posts.count");
68+
}
69+
```
70+
71+
The code above forces the calling code of `countUserPosts()` to handle `IOException`.
72+
How should the caller decide what to do? It does not even know that some property file is read,
73+
this is an implementation detail of the `countUserPosts()` method.
74+
75+
Instead consider an approach like the following:
76+
77+
<!-- we use groogy as languate so the formatting works ;D -->
78+
79+
#### Instead do:
80+
```groovy
81+
public static int countUserPosts() {
82+
try {
83+
Properties userProps = new Properties();
84+
try (FileReader reader = new FileReader("my-users.properties")) {
85+
userProps.load(reader);
86+
}
87+
return userProps.getProperty("user.posts.count");
88+
} catch (IOException e) {
89+
throw new RuntimeException(e);
90+
}
91+
}
92+
```
93+
94+
### Log-Rethrow Anti-Pattern
95+
96+
* Do not only log an exception and rethrow
97+
* Pollutes the log
98+
* Does not add anything but confusion
99+
100+
```groovy
101+
public static void someMethod() {
102+
try {
103+
// ...
104+
doSomeStuff(); // e.message = Oh no! Another exception.
105+
// ...
106+
} catch (ApplicationException e) {
107+
log.error(e);
108+
throw new ApplicationException(e);
109+
}
110+
}
111+
```
112+
113+
When we catch an exception surely we want to be totally sure we have logged it, don't we? So let's
114+
just catch any exception and make sure.
115+
116+
Perfect. Now we have the exception in the log 💪😃!
117+
118+
When we inspected the log we observed "Oh no! Another exception." several times. Why, when did it
119+
occur?
120+
The confusion inceases...
121+
122+
Later, after hours of debugging, we notice the following code:
123+
124+
```groovy
125+
public static void anotherMethod() {
126+
try {
127+
// ...
128+
someMethod();
129+
// ...
130+
} catch (ApplicationException e) { // the re-thrown exception
131+
log.error(e);
132+
throw e;
133+
}
134+
}
135+
```
136+
137+
We caught, logged and threw the same exception over again. Wow, so all the time we only had one
138+
exception and not several?
139+
140+
Thanks to the redundant catching and logging of the same exception within the called method and
141+
after the method, reading the log file was more confusing than it had to be
142+
We would have ended up with only one exception in our log file, stating where it occurred and that
143+
it occured only once.
144+
We would have saved many hours of digging through the code.
145+
**Never** only catch an exception to **log and rethrow** it.
146+
147+
#### Instead do: [catch exception when necessary](#catch-rethrow).
148+
149+
### Exceptions as control flow
150+
151+
**Exceptions as control-flow smell like a _GOTO_ instruction!**<br>
152+
They do not have any scope and can run wild in your code!
153+
154+
![](https://imgs.xkcd.com/comics/goto.png)
155+
156+
* Exceptions are expensive
157+
* Exceptions indicate **exceptional** system state
158+
* catch blocks -> possibilities for _diaper_, _decapitation_, _GOTOs_
159+
160+
Exceptions are slow. It is cheaper to validate first and then proceed than to throw an exception and
161+
handle it.
162+
When using exceptions for control flow, programmers are forced to write catch blocks and the chance
163+
for swallowing an exception or decapitating an exception increases. Developers might forget to catch
164+
an exception or if the exception was added later, legacy code might not be aware of the possibility
165+
and miss some previous type of exception.
166+
167+
Thus, avoid exceptions for expected or likely outcomes. For expected outcomes consider returning a
168+
meaningful result instead. (_Hint: Java Records present an easy way to implement result objects_)
169+
170+
<!-- we use groogy as languate so the formatting works ;D -->
171+
172+
```groovy
173+
public static void isShorterThan(String input, int maxLength) {
174+
if (input.length() >= maxLength) {
175+
throw new LengthException("The string is to long");
176+
}
177+
}
178+
179+
public static void isLongerThan(String input, int minLength) {
180+
if (input.length() <= minLength) {
181+
throw new LengthException("The string is to short");
182+
}
183+
}
184+
185+
public static boolean isZipCode(String code) {
186+
try {
187+
isLongerThan(code, 4);
188+
isShorterThan(code, 6);
189+
return true;
190+
} catch (LengthException e) {
191+
return false;
192+
}
193+
}
194+
```
195+
196+
Replacing exceptions as control flow:
197+
198+
```groovy
199+
public static boolean isShorterThan(String input, int maxLength) {
200+
return input.length() >= maxLength;
201+
}
202+
203+
public static boolean isLongerThan(String input, int minLength) {
204+
return input.length() <= minLength;
205+
}
206+
207+
public static boolean isZipCode(String code) {
208+
return isLongerThan(code, 4) && isShorterThan(code, 6);
209+
}
210+
```
211+
212+
If you **reasonably expect** a value to be present when no value is present, or a variable to have a
213+
specific value, throwing exceptions is fine. For example:
214+
215+
```groovy
216+
public static void doSomeObjectStuff(Object object) {
217+
if (object == null) {
218+
throw new NullPointerException("object is null");
219+
}
220+
// ... some code working with object
221+
}
222+
223+
public static void doStuff() {
224+
Object myObject = createObject();
225+
doSomeObjectStuff(myObject);
226+
}
227+
```
228+
229+
Here we assume that we are given an object in the `doSomeObjectStuff` method. If this is not the
230+
case, something is off and we panic.
231+
232+
**Note:** In those cases we do not `catch` the exception and try
233+
to [handle it as late as possible](#throw-early-handle-late).
234+
235+
## Good practices
236+
237+
### Throw early, handle late
238+
239+
When encountering exceptional circumstances, additional work should be avoided.
240+
We want the application to fail as early as possible.
241+
242+
_**Throw before mutating state. Leave the state unaltered.**_
243+
244+
### When to catch exceptions
245+
246+
Avoid catching exceptions where you cannot act on them.
247+
248+
The diaper and the log-rethrow anti-pattern indicate a caught exception in the wrong place.
249+
There are some cases in which you want to catch exceptions.
250+
251+
#### Catch and Resolve
252+
253+
If you can resolve the exceptional state and return back to a normal state, you can catch the
254+
exception.
255+
However, be careful that you consider this decision wisely as it poses the question: If the
256+
exception can be resolved, is it really exceptional?
257+
If the answer is no, you should revisit your code and consider using a meaningful return value or
258+
whether to split your method with SRP instead of throwing an exception.
259+
260+
#### Catch-Rethrow
261+
262+
It can be useful to catch an exception, do something with it and rethrow that
263+
exception. Only catch-rethrow if you
264+
265+
- **user-friendly message**:
266+
- wrap your exception to provide the user with a friendly and understandable message.
267+
- **unit test a thrown exception**:
268+
- write a unit test that checks for a thrown exception.
269+
- **report debug information in new message**:
270+
- wrap the exception in another exception with useful debug information in the message.
271+
- **get rid of checked exception**:
272+
- [discussed earlier](#checked-exceptions)
273+
274+
#### Global exception handling
275+
276+
So we never want to catch unwanted exceptions. How do we save our users from bombardment with stack
277+
traces?
278+
279+
We learned that we should **never expose stack traces to users** as this is a security issue.
280+
So should I rather swallow exceptions and ignore
281+
the [diaper anti-pattern](#diaper-swallowing-exceptions)?
282+
283+
As a developer, you want to **feel safe and rest assured that we can throw exceptions any time and
284+
anywhere**.
285+
286+
The solution is to create a **application global safety net**, catching all exceptions that occur. A
287+
global exception
288+
handler!
289+
Developers can throw exceptions whenever they want without needing to ensure that they are handled
290+
and without being afraid to expose details to users of your software.
291+
292+
A global exception handler should sit between your software and the
293+
user. [In Spring applications this can be at the REST API level](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ExceptionHandler.html).
294+
As Vaadin computes the view server-side, we can use its exception and error handling classes as
295+
global exception handlers;
296+
See [Routing Exception Handler in Vaadin](https://vaadin.com/docs/v23/routing/exceptions)
297+
and [Custom Exception Handler in Vaadin](https://vaadin.com/docs/v23/routing/exceptions#custom-exception-handlers).
298+
299+
## Scenarios
300+
301+
##### I want to display an error message at the point where the user entered information.
302+
303+
There are a couple of problems you need to address:
304+
305+
1. How can I identify the input location at the place where the error is detected?
306+
2. How expected is the invalid input? How frequently does it occur?
307+
- If it is expected: Why did I decide against an explicit response object?
308+
- If it is unexpected, how can I show a specific error message?
309+
3. What is the closest point where I can show the error message?
310+
4. Catch or do a `Result.of` call?
311+
312+
##### I want to re-direct to a specific error page for certain errors.
313+
314+
When does re-direction make sense? Is it only when navigating, that redirecting to a certain error
315+
page makes sense, or are the other cases?
316+
A different handler can take care of exceptions during navigation. (Does it make sense to subclass
317+
the main exception?)
318+
319+
##### I want to display an error message at a specific location no matter where the exception occurred.
320+
321+
At the owner of that location catch and modify ui.

0 commit comments

Comments
 (0)