Tuesday, September 15, 2015

Java Exception Handling




Throw a type appropriate to your abstraction
Item 72: Favor the use of standard exceptions
IllegalStateException. This is
generally the exception to throw if the invocation is illegal because of the state of
the receiving object. For example, this would be the exception to throw if the
caller attempted to use some object before it had been properly initialized.

When an API throws a checked exception, the objects involved should be left in a consistent, well-defined state. Ideally this will be the state they were in prior to the invocation, in which case this is called failure atomicity.

When an unchecked exception is thrown, this indicates an unrecoverable error, so the same expectation generally does not apply.


Throw early catch late
https://arstechnica.com/information-technology/2014/10/why-throw-early-and-catch-late/

https://softwareengineering.stackexchange.com/questions/231057/exceptions-why-throw-early-why-catch-late
You want to throw an exception as soon as possible because that makes it easier to find the cause. For example, consider a method that could fail with certain arguments. If you validate the arguments and fail at the very beginning of the method, you immediately know the error is in the calling code. If you wait until the arguments are needed before failing, you have to follow the execution and figure out if the bug is in the calling code (bad argument) or the method has a bug. The earlier you throw the exception, the closer it is to its underlying cause and the easier it is to figure out where things went wrong.
The reason exceptions are handled at higher levels is because the lower levels don't know what's the appropriate course of action to handle the error. In fact, there could be multiple appropriate ways to handle the same error depending on what the calling code is. Take opening a file for example. If you're trying to open a config file and it's not there, ignoring the exception and proceeding with the default configuration can be an appropriate response. If you're opening a private file that's vital to the program's execution and it's somehow missing, your only option is probably to close the program.
Wrapping the exceptions in the right types is a purely orthogonal concern.

https://howtodoinjava.com/best-practices/java-exception-handling-best-practices/

3.2. Declare the specific checked exceptions that your method can throw

public void foo() throws Exception { //Incorrect way
}
Always avoid doing this as in above code sample. It simply defeats the whole purpose of having checked exception. Declare the specific checked exceptions that your method can throw. If there are just too many such checked exceptions, you should probably wrap them in your own exception and add information to in exception message. You can also consider code refactoring also if possible.

public void foo() throws SpecificException1, SpecificException2 { //Correct way
}

3.8. Always catch only those exceptions that you can actually handle

catch (NoSuchMethodException e) {
   throw e; //Avoid this as it doesn't help anything
}

Well this is most important concept. Don’t catch any exception just for the sake of catching it. Catch any exception only if you want to handle it or, you want to provide additional contextual information in that exception. If you can’t handle it in catch block, then best advice is just don’t catch it only to re-throw it.

3.10. Use finally blocks instead of catch blocks if you are not going to handle exception

try {
  someMethod();  //Method 2
finally {
  cleanUp();    //do cleanup here
}
This is also a good practice. If inside your method you are accessing some method 2, and method 2 throw some exception which you do not want to handle in method 1, but still want some cleanup in case exception occur, then do this cleanup in finally block. Do not use catch block.

3.20. Document all exceptions in the application with javadoc

3.19. Use template methods for repeated try-catch


https://mp.weixin.qq.com/s/Oo53o8Qhiu8VHH70dTIOuA



正确的封装和传递异常

不要丢失异常栈,因为异常栈对于定位原始错误很关键

要打印异常,就不要抛出,不要两者都做




不要在finally块中抛出异常

如果在finally中抛出异常,将会覆盖原始的异常,如果finally中真的可能会发生异常,那一定要处理并记录它,不要向上抛。




不要使用printStackTrace

要给异常添加上有用的上下文信息,单纯的异常栈,没有太大意义




Throw early catch late

异常界著名的原则,错误发生时及早抛出,然后在获得所以全部信息时再捕获处理.也可以理解为在低层次抛出的异常,在足够高的抽象层面才能更好的理解异常,然后捕获处理





不要使用异常来控制程序逻辑流程

我们总是不经意间这么做了,这样使得代码变更丑陋,使得正常业务逻辑和错误处理混淆不清;而且也可能会带来性能问题,因为异常是个比较重的操作。





及早校验用户的输入

在最边缘的入口校验用户的输入,这样使得我们不用再更底层逻辑中处处校验参数的合法性,能大大简化业务逻辑中不必要的异常处理逻辑;相反,在业务中不如果担心参数的合法性,则应该使用卫语句抛出运行时异常,一步步把对参数错误的处理推到系统的边缘,保持系统内部的清洁


https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java
2. Prefer Specific Exceptions
Therefore make sure to provide them as many information as possible. That makes your API easier to understand. And as a result, the caller of your method will be able to handle the exception better or avoid it with an additional check.
So, always try to find the class that fits best to your exceptional event, e.g. throw a NumberFormatException instead of an IllegalArgumentException. And avoid throwing an unspecific Exception.
public void doNotDoThis() throws Exception {
    ...
}
public void doThis() throws NumberFormatException {
    ...

4. Throw Exceptions With Descriptive Messages
5. Catch the Most Specific Exception First
6. Don’t Catch Throwable
8. Don’t Log and Throw
log.error(e);
throw e;
But it will write multiple error messages for the same exception.
9. Wrap the Exception Without Consuming it
throw new MyBusinessException("A message that describes the error.", e);

Checked vs UnChcked Exception

http://blog.takipi.com/ignore-checked-exceptions-all-the-cool-devs-are-doing-it-based-on-600000-java-projects/

1. “Use exceptions only for exceptional scenarios”

Exceptions cause considerable overhead on the JVM, using exceptions for normal flow control is a source for trouble (Yes, even though many developers abuse it). On our actionable exceptions post, we’ve elaborated on this “normal exceptions” issue.

2. “Use checked exceptions for recoverable conditions and runtime exceptions for programming errors”

This implies that if a developer finds a checked exception unrecoverable, it’s ok to wrap it in an unchecked exception with its state and throw it up the hierarchy for logging and handling.

3. “Avoid unnecessary use of checked exceptions”

Use checked exceptions only when the exception cannot be avoided by properly coding the API and there’s no alternate recovery step.

4. “Favor the use of standard exceptions”

Using standard exceptions from the already extensive Java API promotes readability.

5. “Throw exceptions appropriate to the abstraction”

As you go higher in the hierarchy, use the appropriate exception type.

6. “Document all exceptions thrown by each method”

No one likes surprises when it comes down to exceptions.

7. “Include failure-capture information in detail messages”

Without information about the state the JVM was in, there’s not much you can do to make sure the exception doesn’t happen again. 

8. “Don’t ignore exceptions”

All exceptions should lead to some action, what else do you need them for?
http://javainterviews.scribbleit.in/core-java/when-to-create-custom-checked-and-unchecked-exception-in-java/p/BXxjMw
InterruptedException
Thrown when a thread is waiting, sleeping, or otherwise occupied, and the thread is interrupted, either before or during the activity. Occasionally a method may wish to test whether the current thread has been interrupted, and if so, to immediately throw this exception. The following code can be used to achieve this effect:
  1. if (Thread.interrupted()) // Clears interrupted status!
  2. throw new InterruptedException();
Checked Exceptions should be used when you want to force the user of your API to think how to handle the recoverable exceptional situation. Otherwise, RuntimeException should be your obvious choice for logical errors.
Unchecked/Runtime Exceptions Represents an error in our program’s logic which can not be reasonably recovered from at run time, for example ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException. We do not need to declare/catch such exception in the method signature because these are not expected by any programmer. Custom unchecked exceptions can be created by extending from RuntimeException. One case where it is common practice to throw a RuntimeException is when the user calls a method incorrectly. For example, a method can check if one of its arguments is incorrectly null. If an argument is null, the method might throw a NullPointerException, which is an unchecked exception.
If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception.

What is difference between ClassNotFoundException and NoClassDefFoundError?

First one is checked exception, second one is unchecked error.
https://dzone.com/articles/how-to-properly-use-exceptions-in-java
One of the cornerstones was that I do not use Checked Exceptions.
Exceptions should be for Exceptional cases. These are unexpected scenarios, and usually, will not have a nice easy way of recovery. If there is a sensible recovery option, then do not use an exception; using exceptions to control the flow through your program is bad practice. If we take this to be the case, then the best way to handle an Exception is to alert this out; whether through logs or some other service depends on how you work on your application. You then have to make a choice:
  • Either you keep the application up and have someone manually inspect the application to ensure that it is in the correct state and not damaged (usually a bad idea)
  • Assume your application is damaged and you bring down the instance
The latter option is preferred but is obviously contingent on your architecture being designed to cope with this.
In terms of coding, this is easy to do. If you have an exceptional circumstance, throw an application specific Runtime exception- extend RuntimeException with a name related to your application. This means when tracing logs you know the error is related to code you have written.
Why use runtime and not checked? Checked exceptions lead to ugly code as the exception handling must be propagated upwards, leading to all calling methods having “throws SomeException” on the end, which leads to more catch blocks. The old argument is that this “forces” developers to handle exceptions properly. Anyone on a real code base knows that this does not happen, and Exceptions are routinely ignored, or printed out and then ignored. This isn’t some sign of a terrible developer; it is such a common occurrence that it is a sign that checked Exceptions are broken. If you need further evidence, look to C# and pretty much all of the new JVM languages such as Clojure and Scala have done away with checked exceptions completely.
If you have no sensible way to handle/recover from the exception then throw it up, but first wrap it in a RuntimeException of your own creation. If my application is called “Stockfighter,” I will have a “StockfighterException” to wrap any other Exception:
public class StockfighterException extends RuntimeException{
    public StockfighterException(String error, Exception e) {
        super(error, e);
    }
}
I do this for two reasons; firstly, when I then debug the application I can see where the Exception has come from in my code base and add any useful information. This is immensely useful when debugging. Secondly, as mentioned, I dislike checked exceptions. This means I can then throw it up the stack without polluting all of the methods with “throws XException”.
  • Never use Exceptions for control flow in your application. Use them only for exceptional circumstances
  • Wrap Checked Exceptions in your own app RuntimeException so you know where the Exception has come from
  • Create a top level ExceptionHandler decorating your code to appropriately log or crash the application.
  1. Use Specific Exceptions – Base classes of Exception hierarchy doesn’t provide any useful information, thats why Java has so many exception classes, such as IOException with further sub-classes as FileNotFoundException, EOFException etc. We should always throw and catch specific exception classes so that caller will know the root cause of exception easily and process them. This makes debugging easy and helps client application to handle exceptions appropriately.
  2. Throw Early or Fail-Fast – We should try to throw exceptions as early as possible. Consider above processFile() method, if we pass null argument to this method we will get following exception.
private static void processFile(String file) throws MyException {
  if(file == null) throw new MyException("File name can't be null", "NULL_FILE_NAME");
//further processing
}
  1. Catch Late – Since java enforces to either handle the checked exception or to declare it in method signature, sometimes developers tend to catch the exception and log the error. But this practice is harmful because the caller program doesn’t get any notification for the exception. We should catch exception only when we can handle it appropriately. For example, in above method I am throwing exception back to the caller method to handle it. The same method could be used by other applications that might want to process exception in a different manner. While implementing any feature, we should always throw exceptions back to the caller and let them decide how to handle it.
  1. Closing Resources 
  2. Logging Exceptions
  3. Single catch block for multiple exceptions – Most of the times we log exception details and provide message to the user, in this case we should use java 7 feature for handling multiple exceptions in a single catch block. This approach will reduce our code size and it will look cleaner too.
  4. Using Custom Exceptions – It’s always better to define exception handling strategy at the design time and rather than throwing and catching multiple exceptions, we can create a custom exception with error code and caller program can handle these error codes. Its also a good idea to create a utility method to process different error codes and use it.
  5. Naming Conventions and Packaging – When you create your custom exception, make sure it ends with Exception so that it will be clear from name itself that it’s an exception. Also make sure to package them like it’s done in JDK, for example IOException is the base exception for all IO operations.
  6. Use Exceptions Judiciously – Exceptions are costly and sometimes it’s not required to throw exception at all and we can return a boolean variable to the caller program to indicate whether an operation was successful or not. This is helpful where the operation is optional and you don’t want your program to get stuck because it fails. For example, while updating the stock quotes in database from a third party webservice, we may want to avoid throwing exception if the connection fails.
  7. Document the Exceptions Thrown – Use javadoc @throws to clearly specify the exceptions thrown by the method, it’s very helpful when you are providing an interface to other applications to use.
http://www.yegor256.com/2015/07/28/checked-vs-unchecked-exceptions.html
Joshua Bloch, in Effective Java, says to "use checked exceptions for recoverable conditions and runtime exceptions for programming errors."

DontUseExceptionsForFlowControl
Exceptions (in Java and C++) are like non-local goto statements. As such they can be used to build general control flow constructs. For example, you could write a recursive search for a binary tree which used an exception to return the result:
 void search( TreeNode node, Object data ) throws ResultException {
 if (node.data.equals( data ))
  throw new ResultException( node );
 else {
  search( node.leftChild, data );
  search( node.rightChild, data );
 }
 }
The cute trick here is that the exception will break out of the recursion in one step no matter how deep it has got. 

This violates the PrincipleOfLeastAstonishment. This makes it harder for programmers to read. There are existing control structures whose sole purpose is to handle these types of operations. Be kind to the developers who follow you and use a standard approach rather than being creative.
Perhaps more important, it's not what compiler implementors expect. They expect exceptions to be set up often but thrown rarely, and they usually let the throw code be quite inefficient. Throwing exceptions is one of the most expensive operations in Java, surpassing even new. On the other hand, don't forget the first rule of optimization (RulesOfOptimization).

Another example might be code like:
 try {
 for (int i = 0; /*wot no test?*/ ; i++)
  array[i]++;
 } catch (ArrayIndexOutOfBoundsException e) {}



This is equivalent to:
 for (int i = 0; i < array.length; i++)
  array[i]++;
Some people advocate the exception version as being faster for sufficiently large arrays. The belief is that it avoids doing the bounds check against array.length twice.
In practise it makes almost every programmer who sees it barf. The extra check can be optimised out by a good JVM anyway, so nothing is gained (admittedly not all JVMs are that good). Alternatively, some JVMs have the option to manually disable array bounds checking, for speed, and this code will break under such an option. -- DaveHarris

Modern JVMs can optimise away the bounds check if they can see that the loop will never go beyond the end of the array. The version using exceptions is impossible to optimise in this way. --NatPryce

https://www.javacodegeeks.com/2012/03/why-should-you-use-unchecked-exceptions.html
Unchecked exceptions :
  • represent defects in the program (bugs) – often invalid arguments passed to a non-private method. To quote from The Java Programming Language, by Gosling, Arnold, and Holmes : “Unchecked runtime exceptions represent conditions that, generally speaking, reflect errors in your program’s logic and cannot be reasonably recovered from at run time.”
  • are subclasses of RuntimeException, and are usually implemented using IllegalArgumentException, NullPointerException, orIllegalStateException
  • a method is not obliged to establish a policy for the unchecked exceptions thrown by its implementation (and they almost always do not do so)
Checked exceptions :


  • represent invalid conditions in areas outside the immediate control of the program (invalid user input, database problems, network outages, absent files)
  • are subclasses of Exception
  • a method is obliged to establish a policy for all checked exceptions thrown by its implementation (either pass the checked exception further up the stack, or handle it somehow)
Clean code: a handbook of agile software craftsmanship


The debate is over. For years Java programmers have debated over the benefits and liabilities of checked exceptions. When checked exceptions were introduced in the first version of Java, they seemed like a great idea. The signature of every method would list all of the exceptions that it could pass to its caller. Moreover, these exceptions were part of the type
of the method. Your code literally wouldn’t compile if the signature didn’t match what your code could do.

At the time, we thought that checked exceptions were a great idea; and yes, they can yield some benefit. However, it is clear now that they aren’t necessary for the production of robust software. C# doesn’t have checked exceptions, and despite valiant attempts, C++ doesn’t either. Neither do Python or Ruby. Yet it is possible to write robust software in all of these languages. Because that is the case, we have to decide—really—whether checked exceptions are worth their price.

Checked exceptions can sometimes be useful if you are writing a critical library: You must catch them. But in general application development the dependency costs outweigh the benefits

If a client can reasonably be expected to recover from an exception, make it a checked exception. If a client cannot do anything to recover from the exception, make it an unchecked exception.

If you have to read an XML file using a DOM Parser, we need to deal with some checked exceptions[5] like ParserConfigurationException, SAXException and IOException . The API developer thought that if the XML was invalid, they should notify so that the consumer of the API(ie, the application developer) can decide how to handle the situations.

Now, If You have some alternatives to proceed with the normal logic, you could do that, other wise you should catch these checked exceptions and throw and Unchecked exception. This way the method signatures will be clean also, we are stating that if the XML is invalid we are can not do much, and we are stopping the processing. Let the error handler written at the top layer take the appropriate decision on what to do.

http://www.artima.com/interfacedesign/CheckedExceptions.html
  • Guideline: Use a checked exception for conditions client code can reasonably be expected to handle.
  • Java requires that a method declare in a throws clause the exceptions that it may throw
  • A method's throws clause indicates to client programmers what exceptions they may have to deal with when they invoke the method
  • Only exceptions that will cause a method to complete abruptly should appear in its throws clause: 
  • Only checked exceptions need appear in throws clauses
  • Whether an exception is "checked" or "unchecked" is determined by its position in the hierarchy of throwable classes:


Exceptions In Your Face

  • Placing an exception in a throws clause forces client programmers who invoke your method to deal with the exception, either by:
    • Catching it, or
    • Declaring it in their own throws clause
  • Errors: For the JVM and Java API
  • To make a checked exception, subclass Exception but not RuntimeException
  • To make an unchecked exception, subclass RuntimeException 

  • Why Checked Exceptions?

  • From the JLS: "This compile-time checking for the presence of exception handlers is designed to reduce the number of exceptions which are not properly handled."
  • If you are throwing an exception for an abnormal condition that you feel client programmers should consciously decide how to handle, throw a checked exception.
  • Unchecked exceptions indicate software bugs.
  • Precisely because unchecked exceptions usually represent software bugs, they often can't be handled somewhere with more context.

http://tutorials.jenkov.com/java-exception-handling/checked-or-unchecked-exceptions.html
Pro Checked Exceptions:
Compiler enforced catching or propagation of checked exceptions make it harder to forget handling that exception.

Pro Checked Exceptions:
Unchecked exceptions makes it easier to forget handling errors since the compiler doesn't force the developer to catch or propagate exceptions (reverse of 1).

Pro Unchecked Exceptions:
Checked exceptions that are propagated up the call stack clutter the top level methods, because these methods need to declare throwing all exceptions thrown from methods they call.

Pro Checked Exceptions:
When methods do not declare what unchecked exceptions they may throw it becomes more difficult to handle them.

Pro Unchecked Exceptions:
Checked exceptions thrown become part of a methods interface and makes it harder to add or remove exceptions from the method in later versions of the class or interface.
Try-with-resources in Java 7
When the try block finishes the FileInputStream will be closed automatically. This is possible becauseFileInputStream implements the Java interface java.lang.AutoCloseable. All classes implementing this interface can be used inside the try-with-resources construct.
If an exception is thrown both from inside the try-with-resources block, and when theFileInputStream is closed (when close() is called), the exception thrown inside the try block is thrown to the outside world. The exception thrown when the FileInputStream was closed is suppressed. This is opposite of what happens in the example first in this text, using the old style exception handling (closing the resources in the finally block).

The resources will be closed in reverse order of the order in which they are created / listed inside the parentheses. First the BufferedInputStream will be closed, then the FileInputStream.

Custom AutoClosable Implementations

catch(SQLException | IOException e)
http://tutorials.jenkov.com/java-exception-handling/exception-wrapping.html
Exception wrapping is wrapping is when you catch an exception, wrap it in a different exception and throw that exception.

you may not want your top level components to know anything about the bottom level components, nor the exceptions they throw. For instance, the purpose of DAO interfaces and implementations is to abstract the details of data access away from the rest of the application. Now, if your DAO methods throw SQLException's then the code using the DAO's will have to catch them. What if you change to an implementation that reads the data from a web service instead of from a database? Then you DAO methods will have to throw both RemoteException and SQLException. And, if you have a DAO that reads data from a file, you will need to throw IOException too. That is three different exceptions, each bound to their own DAO implementation.
To avoid this your DAO interface methods can throw DaoException. In each implementation of the DAO interface (database, file, web service) you will catch the specific exceptions (SQLException, IOException, RemoteException), wrap it in a DaoException, and throw the DaoException. Then code using the DAO interface will only have to deal with DaoException's. It does not need to know anything about what data access technology is used in the various implementations.
http://tutorials.jenkov.com/java-exception-handling/exception-enrichment.html
In exception enrichment you do not wrap exceptions. Instead you add contextual information to the original exception and rethrow it. Rethrowing an exception does not reset the stack trace embedded in the exception.

Wrapping Non-Enrichable Exceptions
Unchecked EnrichableException

Fail Safe Exception Handling
The last exception thrown in a try-catch-finally block is
the exception that will be propagated up the call stack.
All earlier exceptions will disappear.

One way to do so is to make sure that the last exception thrown contains all previously thrown exceptions. That way they are all available to the developer investigating the error cause. This is how our Java persistence API, Mr Persister, implements transaction exception handling.
By the way, the try-with-resources features in Java 7 makes it easier to implement fail safe exception handling.

Pluggable Exception Handlers
Pluggable exception handlers are most effective in situations where the exceptions occurring can be handled sensibly in different ways. 
public class WrappingHandler implements ExceptionHandler{
    public void handle(Exception e, String message){
        throw new RuntimeException(message, e);
    }
}
public class CollectingHandler implements ExceptionHandler{
    List exceptions = new ArrayList();

    public List getExceptions(){ return this.exceptions; }

    public void handle(Exception e, String message){
        this.exceptions.add(e);

        //message is ignored here, but could have been
        //collected too.
    }
}
http://tutorials.jenkov.com/java-exception-handling/logging-where-to-log-exceptions.html
Exception Enrichment

To overcome the lack of details available at the top level (and also to some degree at the mid level), you can enrich the exceptions as they propagate up the call stack towards the top level.

I recommend using top level logging wherever possible, since it is easiest to code and maintain, and also to add later in the development process if you do not want to be bothered with it in the beginning. You may run into situations where an exception must be caught and dealt with at a lower level, but these situations don't happen often in my experience. Most often, when an exception occurs that request or event handling is just skipped, and the exception logged along with sufficient information to determine the cause of the error, or reproduce it at least.
Validation - Throw Exceptions Early
When receiving input that needs to be validated before it can be used, validate all input before using any of it. You should not change any state in the application or attached systems until all input data has been validated. That way you avoid leaving the application in a half valid state.
https://blog.eood.cn/exception
这是处理不可预见异常的非常有用的一种方式。应用初成,难免考虑不到所有需要处理的异常。可以用全局异常处理记录日志、发现问题。
public class ExceptionHandlerExample {
    public static void main(String[] args) throws Exception {

        Handler handler = new Handler();
        Thread.setDefaultUncaughtExceptionHandler(handler);

        Thread t = new Thread(new MyThread(), "My Thread");
        t.start();

        Thread.sleep(100);

        throw new RuntimeException("Thrown from Main");
    }

}

class Handler implements Thread.UncaughtExceptionHandler {
    public void uncaughtException(Thread t, Throwable e) {
        System.out.println("Throwable: " + e.getMessage());
        System.out.println(t.toString());
    }
}

class MyThread implements Runnable {
    public void run() {
        throw new RuntimeException("Thrown From Thread");
    }
}

http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/
Exceptions.propagate() helps; it only wraps the exception if it's checked
public static RuntimeException propagate(Throwable t) {
    if (t instanceof RuntimeException) {
        throw (RuntimeException) t;
    } else if (t instanceof Error) {
        throw (Error) t;
    } else {
        throw new RuntimeException(t); // NOPMD
    }
}

public static <T> Observable<T> error(Throwable exception) {
    return create(new OnSubscribeThrow<T>(exception));
}
http://www.jianshu.com/p/70fcf0bb40db
以一个简化了的用户登录的鉴权流程,流程大体如下:
  • 首先尝试本站鉴权,如果失败,再尝试twiter的方式恢复;
  • 之后再进行Two Factor认证;

快速实现

按照流程定义,可以快速实现第一个版本。这段代码充满了很多的坏味道,职责不单一,复杂的异常分支处理,流程的脉络不够清晰等等,接下来我们尝试一种很特别的重构方式来改善设计。
public boolean authenticate(String id, String passwd) {
  User user = null;    
  try {
   user = login(id, passwd);
  } catch (AuthenticationException e) {
    try {
      user = twiterLogin(id, passwd);
    } catch (AuthenticationException et) {
      return false;
    }
  }

  return twoFactor(user);
}

解决思路

logintwiterLogin生产User对象,扮演生产者的角色;而twoFactor消费User对象,扮演消费者的角色。
最难处理的就是logintwiterLogin可能会存在异常处理。正常情况下它们生产User对象,而异常情况下,则抛出异常。
重构的思路在于将异常处理更加明晰化,让生产者与消费者之间的关系流水化。为此,可以将User对象,可能抛出的异常进行容器化,它们形成互斥关系,不能共生于这个世界。

容器化

public interface Try<T> {
  static <T, E extends Exception> 
  Try<T> trying(ExceptionalSupplier<T, E> s) {
    try {
      return new Success<>(s.get());
    } catch (Exception e) {
      return new Failure<>(e);
    }
  }

  boolean isFailure();

  default boolean isSuccess() {
    return !isFailure();
  }

  T get();
}
其中,SuccessFailure包内私有,对外不公开。
final class Success<T> implements Try<T> {
  private final T value;

  Success(T value) {
    this.value = value;
  }

  @Override
  public boolean isFailure() {
    return false;
  }

  @Override
  public T get() {
    return value;
  }
}
import java.util.NoSuchElementException;

final class Failure<T> implements Try<T> {
  private final Exception e;

  Failure(Exception e) {
    this.e = e;
  }

  @Override
  public boolean isFailure() {
    return true;
  }

  @Override
  public T get() {
    throw new NoSuchElementException("Failure.get");
  }
}

生产者

JDK8标准库中不一样,它能处理受检异常。
@FunctionalInterface
public interface ExceptionalSupplier<T, E extends Exception> {
  T get() throws E;
}

第一次重构

import static Try.trying;

public boolean authenticate(String id, String passwd) {
  Try<User> user = trying(() -> login(id, passwd));
  if (user.isFailure()) {
    user = trying(() -> twiterLogin(id, passwd));
  }

  return user.isSuccess() && twoFactor(user.get());
}

链式DSL

上述trying的应用,使用状态查询Try.isFailure/isSuccess方法显得有些笨拙,可以通过构造链式的DSL改善设计。
public interface Try<T> {
  ......

  <U> Try<U> recover(Function<Exception, Try<U>> f);
}
final class Success<T> implements Try<T> {
  ......

  @Override
  @SuppressWarnings("unchecked")
  public <U> Try<U> recover(Function<Exception, Try<U>> f) {
    return (Try<U>)this;
  }
}
final class Failure<T> implements Try<T> {
  private final Exception e;

  Failure(Exception e) {
    this.e = e;
  }

  @Override
  public <U> Try<U> recover(Function<Exception, Try<U>> f) {
    try {
      return f.apply(e);
    } catch (Exception e) {
      return new Failure<U>(e);
    }
  }
}

第二次重构

使用recover关键字,进一步地改善表达力。首先试图login生产一个User,如果失败从twiterLogin中恢复;最后由twoFactor消费User对象。
public boolean authenticate(String id, String passwd) {
  Try<User> user = trying(() -> login(id, passwd))
      .recover(e -> trying(() -> twiterLogin(id, passwd)));    

  return user.isSuccess() && twoFactor(user.get());
}

彻底链化

public interface Try<T> {
  ......

  default T getOrElse(T defaultValue) {
    return isSuccess() ? get() : defaultValue;
  }

  <U> Try<U> map(Function<T, U> f);
}
final class Success<T> implements Try<T> {
  ......

  @Override
  public <U> Try<U> map(Function<T, U> f) {
    try {
      return new Success<U>(f.apply(value));  
    } catch (Exception e) {
      return new Failure<U>(e);
    }
  }
}
final class Failure<T> implements Try<T> {
  ......

  @Override
  @SuppressWarnings("unchecked")
  public <U> Try<U> map(Function<T, U> f) {
    return (Try<U>)this;
  }
}

第三次重构

public boolean authenticate(String id, String passwd) {
  return trying(() -> login(id, passwd))
    .recover(e -> trying(() -> twiterLogin(id, passwd)))
    .map(user -> twoFactor(user))
    .getOrElse(false);
}

应用Scala

Java8 Lambda表达式() -> login(id, passwd)中空的参数列表颇让人费解,但这是Java8语言本身限制的,应用Scala表达力可进一步提高。
import scala.util.Try

def authenticate(id: String, passwd: String): Boolean = {
  Try(login(id, passwd))
    .recover{ case e: => twiterLogin(id, passwd) }
    .map(twoFactor)
    .getOrElse(false)
}

Try的本质

TryMonad的一个应用,使得异常的处理可以在流水线上传递。使用ScalaTry简化实现版本是这样的。
sealed abstract class Try[+T] {
  def isSuccess: Boolean
  def get: T
}

final case class Failure[+T](val exception: Throwable) extends Try[T] {
  def isSuccess: Boolean = false
  def get: T = throw exception
}


final case class Success[+T](value: T) extends Try[T] {
  def isSuccess: Boolean = true
  def get = value
}

object Try {
  def apply[T](r: => T): Try[T] = {
    try Success(r) catch {
      case NonFatal(e) => Failure(e)
    }
  }
}

Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts