如何按照Uncle Bob的规则(推荐)在一种方法中正确使用一个try-catch块?

How to follow Uncle Bob's rule(recommendation) to use one try-catch block in one method correctly?

比如我有一个方法

void process(String userId) {
  if(userId == null) throw new IlligalArgumentException("Usesr ID is required);

  User user = userService.findUserById(userId);

  if(user == null) throw new UserNotFoundException("User with ID: "+ userId +" not found");
  
  try {
     DataResponse response = analyticsAPI.loadAnalytics(userId, user.getDob(), user.getFirstName());  

     //logic
   } catch(AnalyticsAPIException e) {
     //logic
   }
 }
  1. IlligalArgumentException未检查 异常
  2. UserNotFoundException未检查 异常
  3. AnalyticsAPIExceptionchecked 异常

我读到最好的做法是从 try 开始该方法并以 catch 结束,而不是在一个方法中乘以 try-catch 块。

Prefer exceptions to error codes We prefer exceptions to error codes because they are more explicit. When dealing with try / catch, we should not add more logic in a function than the try / catch block, so that function does one thing: handle errors. Recommendation: don’t use nested try / catch.

像这样:

void process(String userId) {
  try {
      if(userId == null) throw new IlligalArgumentException("Usesr ID is required);
    
      User user = userService.findUserById(userId);
    
      if(user == null) throw new UserNotFoundException("User with ID: "+ userId +" not found");
      
         DataResponse response = analyticsAPI.loadAnalytics(userId, user.getDob(), user.getFirstName());  

         //logic
       } catch(AnalyticsAPIException e) {
         //logic
       }
     }

但看起来很奇怪。我在 try-catch 块中抛出一个异常,希望它不会在 catch 中被处理。我希望它会被抛到调用该方法的服务之上。

接下来我可以做:

public void process(String userId) {
          try {
              if(userId == null) throw new IlligalArgumentException("Usesr ID is required);
            
              User user = userService.findUserById(userId);
            
              if(user == null) throw new UserNotFoundException("User with ID: "+ userId +" not found");
              
              DataResponse response = callApi(userId, user.getDob(), user.getFirstName());  
        
              //logic
             }
    
private DataResponse callApi(String userId, Date dob, String firstName){
               try {
                 return analyticsAPI.loadAnalytics(userId, user.getDob(), user.getFirstName());  
               } catch(AnalyticsAPIException e) {
                 //logic
               }
         }

但它并不总是有效。那么,什么更好呢?

unclebob 建议您在 try 或 catch 块中没有语句列表。相反,您应该将“正常”情况与异常情况分开。目标是分离不同的抽象级别。

为了避免名称冲突,我经常在“正常”案例前加上“尝试”一词。 我也经常用自己的方法将 try/catch 分开,以保持重点。例如

“处理”方法应该关注“处理用户(userId)”的含义。它是一个抽象级别,如果将它与其他方法分开,它更容易阅读和理解。

void process(String userId) {
    User user = getUserById(userId);

    loadAnalytics(user);
}

getUserById 仅关注您希望通过用户 ID 获取用户时所需的逻辑。

void void getUserById(String userId){
    if(userId == null) throw new IlligalArgumentException("Usesr ID is required");
    
    User user = userService.findUserById(userId);
    if(user == null) throw new UserNotFoundException("User with ID: "+ userId +" not found");
    
    return user;
}

理解“过程”的方法不一定要懂 加载分析可能会导致异常状态或什至如何处理它们。

但是当您深入研究 loadAnalytics 方法时,您想知道它是如何工作的。现在您可以立即看到加载分析可能会导致异常状态。该方法专注于异常处理,因为它只包含 try/catch 而您还关注可能发生的异常类型。

void loadAnalytics(User user){
    try {
     tryLoadAnalytics(user);
    } catch(AnalyticsAPIException e) {
     handleAnalyticsError(e);
    }
}

我经常使用“try”前缀来避免名称冲突并明确方法可能会失败。

void tryLoadAnalytics(){
    DataResponse response = callApi(userId, user.getDob(), user.getFirstName());  
    
    //logic
}

与“正常”情况一样,异常处理是分开的,因此您可以专注于特定异常的处理方式。

void handleAnalyticsError(AnalyticsAPIException e){
    //logic
}