エラー処理の悪い習慣

WBOY
リリース: 2024-09-03 18:33:12
オリジナル
583 人が閲覧しました

Error Handling Bad Practices

この記事は、以前の 3 ステップのエラー処理記事の続編です。

この記事では、エラー処理における悪い習慣と、つまずいた場合のクイックフィックスをいくつか紹介したいと思います
コードベース内でそれらを横断します。

免責事項、このブログ投稿は単なる個人的な意見であり、特定の使用例に合わせて推奨事項を調整する必要があります。

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");
    }
  }
ログイン後にコピー

この例では、なぜ一部のユーザーが正しい hello メッセージを取得できないと不満を言うのか不思議に思うかもしれませんが、エラーは見られず、ユーザーが単に混乱しているだけだと考えるかもしれません。

オプション 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.例外の飲み込み - 反応性ストリームによる

例 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));
  }
ログイン後にコピー

2. 同じ例外のスタックトレースを繰り返しログに記録する

サイレントに例外をドロップすることの正反対は、同じ例外を複数回ログに記録することです。これは、追加の意味を提供せずに、数百行または数千行のスタックトレースとログを混同するため、悪い習慣です。

私の最悪の例では、ログ内で同じスタックトレースが 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 ステップのエラー処理」で説明しているため、ここではコードを再度示しませんが、推奨事項を示します。

  • エラーをログに記録し、ユーザーに適切なエラー メッセージを返すグローバル エラー ハンドラーを用意します。
  • コードではスタックトレースを避けますが、
    • 意味のあるメッセージを使用してカスタム例外またはランタイム例外で例外をラップします
    • エラーメッセージをログに記録します (スタックトレース全体ではありません)
    • グローバル エラー ハンドラーでのみスタックトレースをログに記録します

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
}
ログイン後にコピー

チェック例外を使用すると、呼び出し元のコードで処理する必要があるため、他のパターンを使用することが難しくなります。関数型プログラミングまたはリアクティブ ストリーム、または春のグローバル エラー ハンドラー。

例外:
チェックされた例外は便利です。ライブラリ コード内で、ユーザーに例外の処理を強制したい場合。

緩和

呼び出し元の回復が合理的に期待できないシナリオでは、チェックされていない例外を使用します。

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 中国語 Web サイトの他の関連記事を参照してください。

ソース:dev.to
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!