首页  >  文章  >  Java  >  错误处理不良做法

错误处理不良做法

WBOY
WBOY原创
2024-09-03 18:33:12578浏览

Error Handling Bad Practices

本文是我之前的三步错误处理文章的后续文章。

在本文中,我想展示一些错误处理方面的不良做法,如果您遇到困难,也许还有一些快速修复
在你的代码库中跨越它们。

免责声明,这篇博文仅代表个人意见,您需要根据您的具体用例调整建议。

1.吞咽异常

我经常偶然发现异常被默默忽略的代码库。也许开发人员很着急或者想要稍后处理错误情况?无论如何,这是一个不好的做法,因为如果发生错误:

  • 用户不会得到任何反馈,可能会认为操作成功。
  • 没有要调查的堆栈跟踪或错误消息。
  • 有时甚至没有正确的日志条目,几乎不可能检测到错误。

不好的例子:

  /**
   * Reads some data from another service or repo and returns it.
   * The repo might throw an exception, but we don't care.
   */
  public Mono<String> getHello() {
    try {
      return helloRepository.getHello()
          .map("Hello %s"::formatted);
    } catch (Exception e) {
      // do nothing
      return Mono.just("Hello World");
    }
  }

在这个例子中,我们可能想知道为什么有些用户抱怨没有收到正确的问候消息,但我们没有看到任何错误,我们可能认为用户只是感到困惑。

选项 1 - 保留 try-catch 并处理错误

在发生错误的地方进行处理,例如如果可能的话已经在存储库中。
如果您需要在这里处理它,至少记录错误并返回正确的错误消息。

最少的重构

  • 记录错误(也可以选择堆栈跟踪)
  • 返回正确的错误消息
  public Mono<String> getHello() {
    try {
      return helloRepository.getHello()
          .map("Hello %s"::formatted);
    } catch (Exception e) {
      log.error("Error reading hello data from repository - {}", e.getMessage(), e);
      return Mono.error(new RuntimeException("Internal error reading hello data %s".formatted(e.getMessage()), e));
    }
  }

注意:

这可能会破坏上游代码,因为您现在返回错误而不是某些默认值。

选项 2 - 适当的反应流重构

更好的方法是处理反应式流 api 内的错误

  public Mono<String> getHelloHandling() {
    return helloRepository.getHello()
      .map("Hello %s"::formatted)
      // wrap the exception in a RuntimeException with a meaningful message
      .onErrorMap(e -> new RuntimeException("Internal error reading hello data from HelloRepository` %s".
          formatted(e.getMessage()), e));
  }

1b.吞咽异常 - 使用反应流

示例一中的模式也出现在反应流中:

  public Mono<String> getHello() {
    return helloRepository.getHello()
        .map("Hello %s"::formatted)
        // do nothing on error, just return a default value
        .onErrorReturn("Hello world");
  }

看起来非常漂亮和干净,对吧?但我们将无法检测到存储库中抛出了错误!
如果有默认值,至少应该写一个错误日志。

减轻

与前面的示例一样,我们将异常包装在另一个异常中,这次是在自定义异常中,这甚至可以更轻松地检测抛出该异常的特定位置

  public Mono<String> getHello2() {
    return helloRepository.getHello()
        .map("Hello %s"::formatted)
        .onErrorMap(
            e -> new CustomException("Error reading hello-data from repository - %s".formatted(e.getMessage()), e));
  }

2. 重复记录同一异常的堆栈跟踪

与默默丢弃异常相反的是多次记录相同的异常。这是一种不好的做法,因为它会将日志与数百或数千行堆栈跟踪混淆,而不提供任何附加含义。

在我最糟糕的例子中,我在日志中发现了五次相同的堆栈跟踪,但根本没有任何有意义的消息。

不好的例子:

控制器:

@RestController
@AllArgsConstructor
@Slf4j
public class HelloController {

  private final HelloService helloService;

  @GetMapping("/hello")
  public Mono<ResponseEntity<String>> hello() {
    return helloService.getHello()
        .map(ResponseEntity::ok)
        .defaultIfEmpty(ResponseEntity.notFound().build())
        .onErrorResume(e -> {
          log.error("Error:", e);
          return Mono.error(e);
        });
  }
}

在服务中:

@Service
@AllArgsConstructor
@Slf4j
public class HelloService {

  private final HelloRepository helloRepository;

  /**
   * Reads some data from another service or repo and returns it.
   */
  public Mono<String> getHello() {
    return helloRepository.getHello()
        .map("Hello %s"::formatted)
        .onErrorResume(e -> {
          log.error("Error:", e);
          return Mono.error(e);
        });
  }
}

...可能在更多地方...

减轻

这在我之前的文章三步错误处理中已经解释过,因此我不会再在这里展示代码,而是推荐:

  • 有一个全局错误处理程序来记录错误并向用户返回正确的错误消息。
  • 在代码中,避免堆栈跟踪,但是
    • 使用有意义的消息将异常包装在自定义异常或运行时异常中
    • 记录错误消息(不是整个堆栈跟踪)
    • 仅在全局错误处理程序中记录堆栈跟踪

3. 捕获通用异常

捕获 Exception 或 Throwable 等通用异常可能会导致意外行为并使调试变得非常困难。最好捕获特定的异常。

坏榜样

  public Mono<String> getHello() {
    try {
      return helloRepository.getHello();
    } catch (Exception e) {
      log.error("Error while fetching hello data", e);
      return Mono.empty();
    }
  }

减轻

捕获特定异常以适当处理不同的错误场景。

  public Mono<String> getHello() {
    try {
      return helloRepository.getHello();
    } catch (SQLException e) {
      return Mono.error(new HelloDataException("Database error while getting hello-data - %s".formatted(e.getMessage()), e));
    } catch (IOException e) {
      // maybe perform a retry?
      return Mono.error(new HelloDataException("IO error while getting hello-data - %s".formatted(e.getMessage()), e));
    }
  }

以及使用反应式流 API 的等效内容

  public Mono<String> getHello() {
    return helloRepository.getHello()
        .onErrorMap(SQLException.class, e -> new HelloDataException("Database error while getting hello-data - %s".formatted(e.getMessage()), e))
        .onErrorMap(IOException.class, e -> new HelloDataException("IO error while getting hello-data - %s".formatted(e.getMessage()), e));
  }

4.(过度)使用检查异常

在我看来,检查异常在 Java 中的错误,它们不是很有用,并且经常导致不良实践和混乱的代码。

坏榜样

public void someMethod() throws IOException, SQLException {
  // some code that might throw an exception
}

对于受检查的异常,您必须在调用代码中处理它们,这使得使用其他模式变得更加困难,例如函数式编程或反应式流或 Spring 中的全局错误处理程序。

例外:
检查异常很有用,例如在库代码中,您想要强制用户处理异常。

减轻

对于调用者无法合理预期恢复的场景使用未经检查的异常。

public void someMethod() {
  try {
    // some code that might throw an exception
  } catch (IOException | SQLException e) {
    throw new RuntimeException("An error occurred - %s".formatted(e.getMessage()), e);
  }
}

Using Exceptions for Control Flow

Using exceptions for control flow makes the code hard to understand and can lead to performance issues.

Bad Example

try {
  int value = Integer.parseInt("abc");
} catch (NumberFormatException e) {
  // handle the case where the string is not a number
}

Mitigation

Use a regular flow control mechanism like an if-statement.

String value = "abc";
if (value.matches("\\d+")) {
  int number = Integer.parseInt(value);
} else {
  // handle the case where the string is not a number
}

Conclusion

In this article I showed some bad practices in error handling and how to mitigate them.
I hope you found this article helpful and maybe you can use some of the recommendations in your codebase and in your next refactoring.

以上是错误处理不良做法的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn