我应该在 if-else 块中抛出异常吗?

Should I throw exceptions in an if-else block?

代码如下:

public Response getABC(Request request) throws Exception {
    Response res = new Response();
    try {
        if (request.someProperty == 1) {
            // business logic
        } else {
           throw new Exception("xxxx");
        }
    } catch (Exception e) {
        res.setMessage(e.getMessage); // I think this is weird
    }
    return res;
}

这个程序运行良好。 我认为它应该重新设计,但是如何?

在 try 块中抛出异常并立即捕获它是没有意义的,除非 catch 块抛出不同的异常。

这样你的代码会更有意义:

public Response getABC(Request request) {
    Response res = new Response();
    if (request.someProperty == 1) {
        // business logic
    } else {
        res.setMessage("xxxx");
    }
    return res;
}

如果您的业务逻辑(在条件为 true 时执行)可能会抛出异常,则只需要 try-catch 块。

如果你没有捕获异常(这意味着调用者必须处理它),你可以不使用 else 子句:

public Response getABC(Request request) throws Exception {
    if (request.someProperty != 1) {
        throw new Exception("xxxx");
    }

    Response res = new Response();
    // business logic
    return res;
}

如果您从方法中抛出异常,那么为什么还要捕获它呢?它要么是你 return 对 "xxxx" 消息的响应,要么是抛出异常让此方法的调用者处理它。

public Response getABC(Request requst) {
    Response res = new Response();
        if(request.someProperty == 1){
            //business logic
        else{
           res.setMessage("xxxx");
        }
    }
    return res;
}

public Response getABC(Request requst) throw Excetpions {
    Response res = new Response();
        if(request.someProperty == 1){
            //business logic
        else{
           throw new Exception("xxxx");
        }
    return res;
}


public void someMethod(Request request) {
    try {
        Response r = getABC(request);
    } catch (Exception e) {
        //LOG exception or return response with error message
        Response response = new Response();
        response.setMessage("xxxx");
        retunr response;
    }

}

首先,重构工作方法时要更加小心——尤其是在执行手动重构时。也就是说,引入一个变量来保存 message 可能是改变设计的一种方式:

public Response getABC(Request requst) throw Excetpions {
    String message = "";
    try{
        if(request.someProperty == 1){
            //business logic
        else{
           message = "xxxx";
        }
    }catch(Exception e){
        message = e.getMessage();
    }
    Response res = new Response();
    res.setMessage(message);
    return res;
}

假设 business logic 在成功时会自己 return。

故意抛出异常然后直接捕获似乎不对, 它可以像这样重新设计,
可以将 throw new Exception("xxxx"); 更改为 res.setMessage("xxxx");
然后可以保留捕获异常的部分,以便捕获业务逻辑内部可能发生的异常。

public Response getABC(Request requst) {
  Response res = new Response();
  try{
      if(request.someProperty == 1){
          //business logic
      else{
         res.setMessage("xxxx");
      }
  }catch(Exception e){
      res.setMessage(e.getMessage);
  }
  return res;
}

为什么在已经抛出 Checked Exception 时使用 try/catch 语句?

Checked exception 通常用于某些语言,如 C++ 或 Java,但不用于 Kotlin 等新语言。我个人限制使用它。

例如,我有一个这样的class:

class ApiService{
    Response getSomething() throw Exception(); 
} 

感觉干净且可读,但破坏了异常处理机制的实用性。实际上,getSomething() 不会抛出已检查的异常,但仍需要像它那样运行?当 ApiService 的上游有人知道如何处理像这样的 unpredictableunpreventable 错误时,这会起作用。如果你真的知道如何处理它,那就继续使用类似下面的示例,否则,Unchecked Exception 就足够了。

public Response getSomething(Request req) throws Exception{
    if (req.someProperty == 1) {
        Response res = new Response();
        // logic 
    } else {
        thows Exception("Some messages go here")
    }
}

我会鼓励这样做:

public Response getSomething(Request req){
if (req.someProperty == 1) {
        Response res = new Response();
        // logic 
        return res;
    } else {
        return ErrorResponse("error message"); // or throw RuntimeException here if you want to
    }
}

为了获得更多见解,我之前提到的 Kotlin 不支持 Checked exception,原因有很多。

下面是JDK实现的接口示例 StringBuilder class:

Appendable append(CharSequence csq) throws IOException;

这个签名说了什么?它说每次我将字符串附加到某些东西(StringBuilder、某种日志、控制台等)时,我必须捕获那些 IOExceptions。为什么?因为它可能正在执行 IO(Writer 也实现了 Appendable)...所以它导致到处都是这种代码:

try {
    log.append(message)
}
catch (IOException e) {
    // Must be safe
}

这不好,请参阅 有效 Java,第 3 版,第 77 项:不要忽略异常。

看看这些链接:

异常机制有三个目的:

  1. 立即禁用正常程序流并返回调用堆栈,直到找到合适的 catch 块。
  2. 以异常类型、消息和可选的附加字段的形式提供上下文,catch 块代码可以使用这些字段来确定操作过程。
  3. 供程序员查看以进行取证分析的堆栈跟踪。 (这在过去制作起来非常昂贵)。

这是一个机制应该具备的很多功能。为了使程序尽可能简单 - 对于未来的维护者 - 因此,我们应该只在 确实 必须使用时才使用此机制。

在您的示例代码中,我希望任何 throw 语句都是非常严重的事情,表明出现了问题并且代码应该在某处处理这种紧急情况。在继续阅读程序的其余部分之前,我需要了解出了什么问题以及它有多严重。这里只是一个奇特的 return 字符串,我会挠头想 "Why was this necessary?" 并且可以更好地花费额外的努力。

所以这段代码并不尽如人意,但如果您也有时间进行完整测试,我只会更改它。更改程序流程可能会引入细微的错误,如果您需要修复任何内容,您需要牢记这些更改。

我认为您可能没有理解try/catch 的要点。该代码使用异常系统将任何异常消息冒泡给调用者。这可能位于嵌套调用堆栈的深处——而不仅仅是您正在查看的 "throws"。

换句话说,示例代码中的 "throws" 声明利用此机制向客户端传递消息,但几乎可以肯定它不是 [=22 的主要预期用户=]. (此外,这是传递此消息的一种草率、有点廉价的方式——它可能会导致混淆)

这个 return 值无论如何都不是一个好主意,因为异常通常没有消息并且可以重新包装......不过总比没有好。异常消息并不是最好的工具,但像这样在高级别处理异常仍然是一个好主意。

我的观点是,如果您重构此代码,请确保查找可能在您的代码库中的任何地方抛出的运行时异常(至少在消息处理期间调用的任何地方)——即便如此,您也应该保留 catch/return 消息作为一个包罗万象的消息,以防出现你没有预料到的运行时异常。您不必 return 错误 "Message" 作为您的响应消息——它可能是一些俏皮的 "We couldn't process your request at this time",但一定要将堆栈跟踪转储到日志中。您目前正在丢弃它。

同样,如果你想获得JVM在失败时返回的特定异常消息,那时候你可以在catch块中使用方法getMessage()或printStackTrace()的try-catch。所以在这里你可以修改你的代码:

public Response getABC(Request request) throws Exception {
    Response res = new Response();
    try {
        if (request.someProperty == 1) {
            // business logic
        } 
    } catch (Exception e) {
        res.setMessage(e.getMessage); 
    }
    return res;
}