適切な Java 例外処理

        問題点について話しましょう。私の責任により、私は多くの異なるサービス (編集を行ったり、コードレビューを行ったり...) を使用する必要があります。エラーの処理やサービスからの転送に関しては、通常、異なるチームがこれらすべてのサービスを作成する必要があります。水に。どのようなコードに許容できないエラー処理があると私が考えるか、そしてそれはどのように処理されるべきだと私が考えるかをお話ししましょう。

        通常、最初は問題はサービス分析の欠陥に隠されています。一般に、エラーがスローされる方法についての参照要件はありません。一般に、これは 2 つの理由で発生します。1 つ目は新しいサービスの開発を急いでいること、2 つ目はアナリストが開発者の経験を信頼していることです。この場合、アナリストは開発者に「わかりました。後でエラー メッセージの例を教えてください。Confluence に添付します。」と伝えるだけです。

        例に移り、開発におけるこのアプローチの結果を見てみましょう。

まずやってはいけないことは、RuntimeException をスローすることです。

public Object badExample() {
  throw new RuntimeException("Something wrong!");
}

展示する:

 

呼び出し元のサービスまたはクライアントは 500 エラーを受け取り、そのリクエストの何が問題なのかを理解する方法がありません。この場合、問題を見つけるのにどれくらい時間がかかると思いますか? コードの量に比例して増加します。メソッド呼び出しのチェーンがある場合、サービス内でメソッドが呼び出されるときに、そのようなメッセージを処理することがさらに困難になります。

どうすれば改善できますか? まず、エラー ハンドラーを作成しましょう。ほとんどすべてのフレームワークがこれをすぐに提供します。この例では、Spring フレームワーク ハンドラーを使用します。

例:

@ControllerAdvice
public class ApiExceptionHandler {

  @ExceptionHandler(value = {RuntimeException.class})
  public ResponseEntity<Object> handler(RuntimeException e) {
    HttpStatus badRequest = HttpStatus.INTERNAL_SERVER_ERROR;
    return new ResponseEntity<>(e.getMessage(), badRequest);
  }
  ...

展示する:

 

メッセージが表示されますが、メッセージの形式は JSON ではなく文字列です。それはすぐに修正します。

        理想的には、プロジェクト内のすべてのサービスが同じエラー メッセージ形式である必要があります。少なくとも 2 つのフィールドは、メッセージ自体と内部整数エラー コードです。メッセージ テキストが不足している理由と、その文字列を処理するために必要なリソースの数は説明する必要はないと思います。

例:

public class ExampleApiError {

  private String message;
  private Integer code;
  private LocalDateTime dateTime;
  ...

        このクラスをエラー ハンドラーに設定します。ただし、前述したように、RuntimeException をスローするのは悪い習慣であるため、エラーをスローする独自のクラスを作成する必要があります。このクラスのコンストラクターでメッセージとエラー コードを渡します。

public class ApiException extends RuntimeException {

  private final int code;

  public ApiException(String msg, int code) {
    super(msg);
    this.code = code;
  }
  ...

すべてが明確になったように見えますが、ここでも問題が始まります。クラス コンストラクターに渡されるパラメーターの作成方法は人それぞれです。メソッド内でメッセージやエラー コードを直接作成するものもあります。

例:

public Object badExample() {
  throw new ApiException("Something wrong!", 10);
}

コード内でそのような場所を見つけるのは依然として困難です。最初に考えたのは、メッセージ用に 1 つのクラス、メッセージ コード用に 2 つ目のクラスという定数クラスを作成することでした。

例:

public static final String SOMETHING_WRONG = "Something wrong";
public static final int SOMETHING_WRONG_CODE = 10;

public Object badExample() {
  throw new ApiException(SOMETHING_WRONG, SOMETHING_WRONG_CODE);
}

        エラーコードの場所がわかっていると、これはすでに優れており、読みやすく、見つけやすくなります。

        ただし、マイクロサービスがない場合は、すべてを 1 つのクラスに保存するのは得策ではない可能性があります。メッセージは大きくなり始めるため、ProductExceptionConstant、PaymentExceptionConstant などで使用されるメソッドの機能に基づいて定数クラスを分離することが最善です。

        しかし、それだけではありません。これは時代遅れに思える人もいるかもしれませんが、定数は列挙型またはインターフェイスとして作成する必要があります。インターフェイス内の変数は、デフォルトでは static、public、final です。私はこれに反対しているわけではありません。肝心なのは、すべてが均一であるべきだということです。インターフェースを通じて何かを始めたら、それを続けてください。混合する必要はありません。プロジェクトの 1 つで、同じチームが 3 つの異なる定数メソッドを使用しているのを見ました。その必要はありません。)

        レビューの過程で私が気づいた実際のプロジェクトの例をもう 1 つ紹介します。

        まず、開発者は、返すすべてのエンティティにはエラー メッセージとエラー コードが含まれ、ステータスは常に 200 になると判断しましたが、これは呼び出し元を誤解させるものだと私は思います。さて、定数の例です。

public enum ErrorsEnum {
    DEFAULT_ERROR(ErrorsConstants.DEFAULT_ERROR, "400"),
    REFRESH_TOKEN_NOT_VALID(ErrorsConstants.REFRESH_TOKEN_NOT_VALID, "400.1"),
    USER_NOT_FOUND(ErrorsConstants.USER_NOT_FOUND, "400.2"),

コード 9 3 \ 4 が欠落しているように思えます。この場合、デフォルトではデジタル Pi を使用できます。

        私の方法に興味があれば、先に進んでください。最も便利なのは、全員にとって拘束力のある契約となるインターフェイスを作成することです。

public interface ExceptionBase {

  String getMsg();

  Integer getCode();
}

また、例外クラスのコンストラクターを変更する必要があります。

public ApiException(ExceptionBase e) {
  super(e.getMsg());
  this.code = e.getCode();
}

さて、例外をスローしようとする人は誰でも、インターフェイスを実装するオブジェクトをコンストラクターに渡す必要があることを理解するでしょう。最も適切な選択は Enum です。

例:

/**
 * This class is intended for declaring exceptions that occur during order processing. code range
 * 101-199.
 */
public enum OrderException implements ExceptionBase {
  ORDER_NOT_FOUND("Order not found.", 101),
  ORDER_CANNOT_BE_UPDATED("Order cannot be updated,", 102);

  OrderException(String msg, Integer code) {
    this.msg = msg;
    this.code = code;
  }

  private final String msg;
  private final Integer code;

  @Override
  public String getMsg() {
    return msg;
  }

  @Override
  public Integer getCode() {
    return code;
  }
}

使用例:

public Object getProduct(String id) {
  if (id.equals("-1")) {
    throw new ApiException(OrderException.ORDER_NOT_FOUND);
  }
...
 

サーバーの応答:

 したがって、次の例外バッグ モデルがあります。

 

ご覧のとおり、複雑なことは何もなく、ロジックは読みやすいです。この例では、文字列を受け入れるコンストラクターを ApiException クラスに残しましたが、信頼性を保つために、これを削除することをお勧めします。

通常、コードの不整合のほとんどは、コード インスペクションの欠如または不十分なインスペクションが原因です。最も一般的な言い訳は、「これは一時的な解決策です。後で修正します」ですが、いいえ、そのようには機能しません。どこに一時的な解決策があり、どこに永続的な解決策があるのか​​をわざわざ調べる人はいません。 。結局のところ、「一時的なものは永続的なもの」なのです。

相互に通信するサービスが多数ある場合は、エラー メッセージ形式を 1 つ作成するだけで、クライアント ライブラリを作成する作業が大幅に簡素化されます。たとえば、Retrofit を使用する場合、ハンドラー コアを作成したら、インターフェイス内のメソッドとそれが受け取るオブジェクトを変更するだけで済みます。

結論は

エラー処理はコードの非常に重要な部分です。エラー処理により、コード内で問題のある領域を簡単に見つけることができ、また、外部クライアントがエンドポイントを使用するときに何が間違っているのかを理解できるようになります。そのため、最初から正しく対処する必要があります。プロジェクト作成の最初の段階 これには十分な注意が払われます。

サンプルコードはこちら

この記事を読んであなたの人生が少しでも楽になることを願っています。すべてがうまくいきました。

おすすめ

転載: blog.csdn.net/qq_28245905/article/details/132100686