この記事は、以前の 3 ステップのエラー処理記事の続編です。
この記事では、エラー処理における悪い習慣と、つまずいた場合のクイックフィックスをいくつか紹介したいと思います
コードベース内でそれらを横断します。
免責事項、このブログ投稿は単なる個人的な意見であり、特定の使用例に合わせて推奨事項を調整する必要があります。
例外が黙って無視されるコードベースによく遭遇します。おそらく開発者は急いでいたか、エラーの場合は後で処理したかったのでしょうか?いずれにせよ、エラーが発生した場合、これは悪い習慣です:
/** * 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"); } }
この例では、なぜ一部のユーザーが正しい hello メッセージを取得できないと不満を言うのか不思議に思うかもしれませんが、エラーは見られず、ユーザーが単に混乱しているだけだと考えるかもしれません。
エラーが発生した場所でエラーを処理します。可能であれば、すでにリポジトリ内にあります。
ここで処理する必要がある場合は、少なくともエラーをログに記録し、適切なエラー メッセージを返してください。
最小限のリファクタリング
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)); }
例 1 のパターンはリアクティブ ストリームにも表示されます:
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)); }
サイレントに例外をドロップすることの正反対は、同じ例外を複数回ログに記録することです。これは、追加の意味を提供せずに、数百行または数千行のスタックトレースとログを混同するため、悪い習慣です。
私の最悪の例では、ログ内で同じスタックトレースが 5 回見つかりましたが、意味のあるメッセージはまったくありませんでした。
コントローラー:
@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)); }
私の意見では、Java の間違いをチェックした例外はあまり役に立たず、悪い習慣やコードが乱雑になることがよくあります。
public void someMethod() throws IOException, SQLException { // some code that might throw an exception }
チェック例外を使用すると、呼び出し元のコードで処理する必要があるため、他のパターンを使用することが難しくなります。関数型プログラミングまたはリアクティブ ストリーム、または春のグローバル エラー ハンドラー。
例外:
チェックされた例外は便利です。ライブラリ コード内で、ユーザーに例外の処理を強制したい場合。
呼び出し元の回復が合理的に期待できないシナリオでは、チェックされていない例外を使用します。
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 中国語 Web サイトの他の関連記事を参照してください。