本文是我之前的三步错误处理文章的后续文章。
在本文中,我想展示一些错误处理方面的不良做法,如果您遇到困难,也许还有一些快速修复
在你的代码库中跨越它们。
免责声明,这篇博文仅代表个人意见,您需要根据您的具体用例调整建议。
我经常偶然发现异常被默默忽略的代码库。也许开发人员很着急或者想要稍后处理错误情况?无论如何,这是一个不好的做法,因为如果发生错误:
/** * 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"); } }
在这个例子中,我们可能想知道为什么有些用户抱怨没有收到正确的问候消息,但我们没有看到任何错误,我们可能认为用户只是感到困惑。
在发生错误的地方进行处理,例如如果可能的话已经在存储库中。
如果您需要在这里处理它,至少记录错误并返回正确的错误消息。
最少的重构
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)); } }
注意:
这可能会破坏上游代码,因为您现在返回错误而不是某些默认值。
更好的方法是处理反应式流 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)); }
示例一中的模式也出现在反应流中:
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)); }
与默默丢弃异常相反的是多次记录相同的异常。这是一种不好的做法,因为它会将日志与数百或数千行堆栈跟踪混淆,而不提供任何附加含义。
在我最糟糕的例子中,我在日志中发现了五次相同的堆栈跟踪,但根本没有任何有意义的消息。
控制器:
@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); }); } }
...可能在更多地方...
这在我之前的文章三步错误处理中已经解释过,因此我不会再在这里展示代码,而是推荐:
捕获 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)); }
在我看来,检查异常在 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 makes the code hard to understand and can lead to performance issues.
try { int value = Integer.parseInt("abc"); } catch (NumberFormatException e) { // handle the case where the string is not a number }
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 }
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中文网其他相关文章!