Original link: phauer.com/2015/checke…

Java is lonely when it comes to exception handling. Because only Java has Checked exceptions. Other languages like C++, Objective-C, C#, Kotlin, Scala, etc. don’t have the concept of Checked exceptions.

1. Root of all evil: Coercive treatment

Go straight to code

private String getContent(Path targetFile) { 
    byte[] content = Files.readAllBytes(targetFile);//compile error: "Unhandled IOException" 
    return new String(content); 
}
Copy the code

You’ll notice that the above code compiles at all, however, because files.readallBytes () throws IOException, which is a Checked Exception. Checked Exception forces the caller to handle the Exception otherwise it will not compile at all.

Oh, no. So what?

  • Option A: Rethrow the exception
  • Option B: Handle exceptions gracefully

2. Throw the exception again

private String getContent(Path targetFile) throws IOException { 
    byte[] content = Files.readAllBytes(targetFile); 
    return new String(content); 
}
Copy the code

Simply, I don’t want to handle exceptions myself, so let the caller do it and declare throws IOException directly on the method signature. But you’ll find that if you do that, it doesn’t work, because this is a private method, and when you call the private method, you’ll find that you still have to handle the exception.

If I throw the exception again, what’s the problem?

2.1.Checked Exception is part of the method signature

Consider the following call stack:

. If we are in the method UserRepository getContent (Path) throws IOException, and we think the ContextMenu menuClicked () method of treatment, we must change the all the method’s signature on the call stack.

And even more, if we want to add an exception later, or change the exception type, or remove the exception entirely, we have to change all of our method signatures. Also, if you use a library interface (such as using the Java8 stream API action.execute ()), you can’t change the method signature without the source code.

2.2. Implementation details are exposed in the method signature

Take a look at the method signature in the example above

UserRepository.getUsers(Path) throws IOException
Copy the code

Given that UserRepository is an interface, do different implementations of the UserRepository interface (e.g. file system-based implementations) all need to throw IOExceptions? Not always. A database implementation might throw a SqlException, or a WebService might throw a TimeoutException.

The bottom line is that exception types (whether or not they actually throw exceptions) are implementation-specific

However, he is forced to deal with IOException even though he knows that the database-based implementation he uses will never throw IOException. More than that, he simply wanted to know what was wrong. Therefore, exceptions thrown should be implementation-independent.

3. Handle exceptions gracefully

Okay, so instead of throwing exceptions up, let’s handle them ourselves, but the key is how do we handle them?

private String getContent(Path targetFile) { 
    try { 
        byte[] content = Files.readAllBytes(targetFile); 
        return new String(content); 
    } catch (IOException e){ 
        // What to do? }}Copy the code

3.1 Is there any good way to handle exceptions

GetContent () is too low-level. At this point, we have no idea what to do with this exception. Perhaps IOException can be handled at a higher level, such as by giving feedback to the user in the UI, but we should do nothing in this approach. However, compile time forces us to handle this exception.

3.2 It is also impossible to recover

In general, we handle exceptions by printing the error log and terminating the current operation or terminating the application if we can’t continue processing. There’s nothing wrong with logging and exiting. But why don’t we just throw the exception out? This is actually the same as the previous treatment. Why should we have to handle exceptions if we can’t recover from them? It makes no sense to be forced to handle exceptions if we just want to quit whatever exception we encounter.

3.3 Further Discussion

Checked Exceptions result in annoying template code (try {} catch () {}). Every time you call a method that throws a Checked Exception, you have to write the template code try-catch-statement.

Compile time forces us to Catch exceptions. This often results in a mixed processing of main logic and errors. But these are separate issues, and logic and error should be dealt with separately in a clear way. Multiple try-catch statements spread error handling throughout the code base.

Even more dangerous is that we simply forget to implement a catch block because we really don’t know how to handle the exception or pretend that it won’t happen at all. Or, we just wanted to quickly test the method call that threw the exception and planned to implement the catch block later, but forgot. An empty catch block eats the exception. When an exception happens, we never know. There was an error in the application and we don’t know why. Then you have to debug for fun.

Finally, many Checked Excpetions are technical exceptions (such as IOExceptions) and do not provide domain-specific semantics to help you resolve them.

3.4. Java 8 Lambda and streams

Checked Exceptions can be annoying when it comes to lambdas and streams. Suppose we want to call a method in a lambda that throws an IOException

List<Path> files = // ...
List<String> contents = files.stream()
       .map(file -> {
           try {
               return new String(Files.readAllBytes(file));
           } catch (IOException e) {
               // Well, we can't reasonably handle the exception here...
           }
       })
       .collect(Collectors.toList());
Copy the code

Because IOExceptions are not declared in lambda’s Function interface (java.util.function.apply ()), we must catch and handle them in lambda. This try-catch template code breaks our code and reduces readability.

4. Solution: Use Unchecked Exception and wrap Checked Exception

Always use Unchecked Exception (RuntimeException) if you are defining your own. If you must handle Checked exceptions, wrap them in your own domain/high-level Exception and re-throw your own. In the getContent () method, we can throw our own RepositoryException, which is based on RuntimeException.

private String getContent(Path targetFile) {
  try {
    byte[] content = Files.readAllBytes(targetFile);
    return new String(content);
  } catch (IOException e) {
    throw new RepositoryException("Unable to read file content. File: " + targetFile, e);
  }
}

public class RepositoryException extends RuntimeException {
//...
}
Copy the code

Now, the caller to getContent () or getUsers () is free to process RepositoryException where it is most appropriate. It is up to the caller, not the compiler. This allows exceptions to be handled uniformly. For example, providing error feedback to users.

Is it possible for the program to be RepositoryException? If you do, you handle it, if you don’t, it doesn’t matter, you can either catch the exception and report it back to the user, or you don’t catch the exception and exit that way.

Indeed, this approach requires more discipline from the programmer. If you want to handle RepositoryException, then you shouldn’t forget to handle RepositoryException. Also, you must record in the javadoc of the method that the method will throw RepositoryException. The compiler is no longer covering for you. You have to be careful with exceptions. But you have more flexibility to write code, and you can get rid of sample code.

5. How to document an Unchecked Exception

However, how do I know that a method will throw an Unchecked Exception? The compiler didn’t tell me. S: Yes, you are responsible for documenting the Exception carefully. There are two ways to do this

  1. Total record in JavaDoc
/ * * *@throwsRepositoryException if ... * /
private String getContent(Path targetFile) {
Copy the code
  1. Declare exceptions in method signatures
private String getContent(Path targetFile) throws RepositoryException {
Copy the code

Some say JavaDoc should be used for Unchecked Exceptions, while throws clause is only used for Checked exceptions. However, I do not agree with this dogmatic approach. I’m more pragmatic at this point: I personally always use the throws clause, simply because you can get better tool support depending on your IDE (for example, complete code when writing an exception in a catch block, highlight a throw method when choosing an exception in a catch block, and vice versa). Alternatively, you can use JavaDoc to get further details about exceptions.

6. Summary

Using UnChecked Exception, it brings freedom and flexibility (we decide for ourselves whether and where to handle exceptions, rather than the compiler). But it also means more responsibility

With great power comes great responsibility

What about Checked Exception?

At the beginning you feel safer with them, but later they prevent you from swimming quickly. At first you feel safer, but then they stop you from swimming faster