Exception Handling - raisercostin/software-wiki GitHub Wiki
2023-11-24
Principles/rules related to exceptions:
- exceptions should only show unexpected behavior
- try not to use them as control flow
- when you see one in the log (stacktrace) this means that the executed code wasn't reviewed by a developer. unexpected situation.
- don't throw information -> in order to fix them
- place where exception was thrown + context + state (in non pure functions, very complex in multi-threading code)
- to be able to reproduce the problem in investigation
- still exceptions are used for control flow (for example FileNotFound exception)
- solution is to always log the stacktrace except if is handled as a normal business scenario (exception transformed in a business response/code)
- even with exception handled (for example FileNotFound, or RuntimeException+file locked in a specific exception message, not generic IOException)
- convert into a business response the strictest interpretation from the exception, root cause, messsage not entire exception, or worse base class Exception or IOException.
- also always log as debug the exception (that will also print the stacktrace)
- don't handle the exception twice: either convert it in business and log as debug or throw it
- corolary:
- the exception variable must always be used -> compilation
- the simplest thing:
throw new RuntimeException(e);
(@SneakyThrow lombok annotation if you have it in project)- you can configure your IDE to always put this code when the catch block is generated
- configure IDE with this block also for any method implementation. 'throw new RuntimeException("Not implemented yet!!!");` This works for both functions with result or without return (void).
- as soon as possible convert the checked exception into a RuntimeException.
- the checked exception breaks the encapsulation of the lowest method and force all the upper layers to know about that
- the handling of the exception most of the time cannot be done at the lower level: for example can be handled by sending an email, logging, retrying, etc
- you can configure your IDE to always put this code when the catch block is generated
- sometimes the handling is to ignore(is a small problem and can recover by ignoring). But in this case I don't want to pollute the log with stacktraces. The best pattern is:
log.warn("Problem {}: {}. If you want full stacktrace enable log.", context, e.getMessage()); //this is simple with the message, no pollution of logs log.debug("Problem {}: {}.", context, e);
- design concerns: exceptions and errors are a mechanism that break the normal flow. it's hard to consider a real system (ideal systems can be imagined) without having exceptions. anyway they break the encapsulation. it's an undeclared way to return a different "response" than the actual response declared in signature. checked exceptions attempted to solve this but the practical world showed that introduce more problems that they solve
- in the end check any codebase for the following anti-patterns:
- compilation: exception not used
- commented out exceptions
- explicit invocations of
printStackTrace()
use slf4j logging framework
- Attention to wrapped exception since the proper handling is usually based on root cause but of course context is also important.
202X
Rules
- idiom:Throw Early Catch Late - (thanks About Lucian)
- principle~Fail Fast
- Don't use Checked Exceptions as they are breaking encapsulation. A lot of calling points will know about the Checked Exception.
- use result, either, option like in https://dev.to/siy/consistent-error-propagation-and-handling-in-java-158j
Solutions
1. Wrap In RuntimeException
}catch(CheckedException e){
throw new RuntimeException("optional additional details",e);
//throw new RuntimeException(e);
//Never use: throw new RuntimeException("message"); as this swallows the original exception.
}
2. Wrap in your WrapperRuntimeException
public class WrappedException extends RuntimeException {
private static final long serialVersionUID = 8487387896248920515L;
public WrappedException() {
super();
}
public WrappedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public WrappedException(String message, Throwable cause) {
super(message, cause);
}
public WrappedException(String message) {
super(message);
}
public WrappedException(Throwable cause) {
super(cause);
}
}
3. Nowrap using apache commons
Throw a checked exception without adding the exception to the throws clause of the calling method. This method prevents throws clause pollution and reduces the clutter of "Caused by" exceptions in the stacktrace.
The use of this technique may be controversial, but exceedingly useful to library developers.
This is an alternative to the more conservative approach of wrapping the checked exception in a RuntimeException.
One downside to using this approach is that the java compiler will not allow invoking code to specify a checked exception in a catch clause unless there is some code path within the try block that has invoked a method declared with that checked exception. If the invoking site wishes to catch the shaded checked exception, it must either invoke the shaded code through a method re-declaring the desired checked exception, or catch Exception and use the instanceof operator. Either of these techniques are required when interacting with non-java jvm code such as Jython, Scala, or Groovy, since these languages do not consider any exceptions as checked.
try {
return invocation(); // throws IOException which is a CheckedException
} catch (Exception e) {
// propagates a checked exception
return org.apache.commons.lang3.exception.ExceptionUtils.rethrow(e);
}
4. Custom made
Usage
- Convert from Callable (that throws CheckedException) to Supplier (that doesn't declare any throws)
return () -> ExceptionUtils.nowrap(()->sup2.call());
- NoWrap Checked Exception
try{ throw new IOException(); }catch(IOException e){ throw ExceptionUtils.nowrap(e); }
- Wrap Checked Exception in RuntimeException
try{ throw new IOException(); }catch(IOException e){ throw ExceptionUtils.wrap(e); }
- NoWrap Checked Exception as result
try{ throw new IOException(); }catch(IOException e){ return ExceptionUtils.rethrowNowrap(e); }
- Wrap Checked Exception as result
try{ throw new IOException(); }catch(IOException e){ return ExceptionUtils.rethrowWrap(e); }
Utility
package org.jedio;
//Copied from apache-commons ExceptionUtils
public class ExceptionUtils {
public static <R> R rethrowNowrap(final Throwable throwable) {
return ExceptionUtils.<R, RuntimeException>sneakyThrow(throwable);
}
public static <R> R rethrowWrap(final Throwable throwable) {
throw new RuntimeException(throwable);
}
public static <R extends RuntimeException> R nowrap(final Throwable throwable) {
return ExceptionUtils.<R, RuntimeException>sneakyThrow(throwable);
}
public static <R extends RuntimeException> R wrap(final Throwable throwable) {
throw new RuntimeException(throwable);
}
@FunctionalInterface
public static interface MyCheckedException<R> {
R apply() throws Throwable;
}
public static <T> T nowrap(MyCheckedException<T> function) {
try {
return function.apply();
} catch (Throwable e) {
throw org.jedio.ExceptionUtils.nowrap(e);
}
}
@SuppressWarnings("unchecked")
// DEV-NOTE: we do not plan to expose this as public API
// claim that the typeErasure invocation throws a RuntimeException
private static <R, T extends Throwable> R sneakyThrow(final Throwable throwable) throws T {
throw (T) throwable;
}
}
Resources
Study more here
- http://wiki.c2.com/?ExceptionPatterns
- https://www.loggly.com/blog/logging-exceptions-in-java/
- https://stackoverflow.com/questions/729379/why-not-use-exceptions-as-regular-flow-of-control
- https://medium.com/@johnmcclean/when-functional-try-outperforms-try-catch-c44e83ec7939
- Checked exceptions are bad: http://www.yegor256.com/2015/07/28/checked-vs-unchecked-exceptions.html
- https://blog.jooq.org/2012/09/14/throw-checked-exceptions-like-runtime-exceptions-in-java/
- http://learningviacode.blogspot.com/2013/06/exceptionutils.html
- guava
- apache commons
- https://dzone.com/articles/fixing-7-common-java-exception-handling-mistakes
- https://stackify.com/best-practices-exceptions-java/