本文是我之前的三步驟錯誤處理文章的後續文章。
在本文中,我想展示一些錯誤處理方面的不良做法,如果您遇到困難,也許還有一些快速修復
在你的程式碼庫中跨越它們。
免責聲明,這篇部落格文章僅代表個人意見,您需要根據您的特定用例調整建議。
我常常偶然發現異常被默默忽略的程式碼庫。也許開發人員很著急或想要稍後處理錯誤情況?無論如何,這是一個不好的做法,因為如果發生錯誤:
/** * 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中文網其他相關文章!