- Small try/catch blocks
- Precise Exception Catching
- Null Pointer Exceptions and Optional
- Return Early
- Lombok
- Avoid wildcard imports
- Log the exception
- AssertJ
- Verify complete and real objects
For inspiration, read more about Offensive Programming and Clean Code.
Small try/catch blocks
Small try blocks are easier to read and make sure the expected exceptions are coming from where they are expected.
Good
try {
something();
} catch () {
// exception came from something()
}
Bad
try {
something_a();
something_b();
something_c();
something_d();
} catch () {
// where did the exception come from?
}
Precise Exception Catching
Good
try {
something();
} catch (ABCDException abcde) {
// handle abcde
}
Bad
try {
something();
} catch (Exception e) {
// handle unexpected exceptions that will never be found, leading to bigger problems
}
Null Pointer Exceptions and Optional
Don’t catch Null Pointer Exceptions. These should not happen and developers should use Optional and null checks if the object can be null. Catching them is awkward.
Return Early
Returning Early reduces complexity of the code, making reviewing easier. It removes else statements and brings the edge cases to the front of the method.
Good
// edge cases
if (!a) {}
if (!b) {}
if (!c) {}
// happy path
do_d();
Bad
if (a) {}
else {
if (b) {}
else {
if (c) {}
else {
do_d();
}
}
}
Lombok
Project Lombok removes so much boilerplate code in Java, making the code readable, requiring less test coverage of generated code, and automatically updating methods when new variables are added.
- Logger:
@Slf4j - Builder:
@Builder - Getters/Setters/ToString:
@Data(read/write) and@Value(read).
Avoid wildcard imports
IDEs like to squash multiple imports into a single line: import com.abc.*. This saves minimal space and makes reading code without an IDE difficult. It also make the code vague, so turn off the setting!
Log the exception
Pass the exception to the logger so the entire stacktrace, message, and other fields can be logged. Plus, there is no String building required.
Good
log.error("Something bad", e);
Bad
log.error("Something bad: {}", e.getMessage());
AssertJ
Use a deep library like AssertJ that has more methods for comparison and reals more naturally.
Verify complete and real objects
Avoid being lazy and using any() or any(Object.class). These satisfy code coverage but miss the goal of unit testing: preventing regressions. Pass the entire object that you expect to receive.
Similarly, when asserting for correctness of the returned value, use isEqualTo() and check the entire object. When new fields are added, that unit tests will fail, requiring an update. For imprecise fields like time, AssertJ has isCloseTo().