Rumah >Java >javaTutorial >Ralat Mengendalikan Amalan Buruk

Ralat Mengendalikan Amalan Buruk

WBOY
WBOYasal
2024-09-03 18:33:12607semak imbas

Error Handling Bad Practices

Artikel ini adalah susulan pada artikel pengendalian ralat 3 langkah saya sebelum ini.

Dalam artikel ini saya ingin menunjukkan beberapa amalan buruk dalam pengendalian ralat dan mungkin beberapa pembaikan cepat, jika anda tersandung
merentasi mereka dalam pangkalan kod anda.

Penafian, catatan blog ini hanyalah pendapat peribadi dan anda perlu melaraskan pengesyoran kepada kes penggunaan khusus anda.

1. Pengecualian menelan

Saya kerap terjumpa pangkalan kod yang pengecualian diabaikan secara senyap. Mungkin pembangun tergesa-gesa atau mahu mengendalikan kes ralat kemudian hari? Bagaimanapun, ini adalah amalan yang tidak baik kerana jika ralat berlaku:

  • pengguna tidak akan mendapat sebarang maklum balas dan mungkin menganggap operasi itu berjaya.
  • tiada surih tindanan atau mesej ralat untuk disiasat.
  • kadangkala tiada entri log yang betul, menjadikannya hampir mustahil untuk mengesan ralat.

Contoh Buruk:

  /**
   * 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");
    }
  }

Dalam contoh ini, kami mungkin tertanya-tanya mengapa sesetengah pengguna mengeluh kerana tidak mendapat mesej helo yang betul, tetapi kami tidak melihat sebarang ralat, kami mungkin menganggap pengguna hanya keliru.

Pilihan 1 - Simpan tangkapan cuba dan kendalikan ralat

Kendalikan ralat di tempat ia berlaku, mis. sudah ada dalam Repositori jika boleh.
Jika anda perlu mengendalikannya di sini, sekurang-kurangnya log ralat dan kembalikan mesej ralat yang betul.

Pemfaktoran semula minima

  • log ralat (pilihan juga surih tindanan)
  • kembali mesej ralat yang betul
  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));
    }
  }

NOTA:

Ini mungkin memecahkan kod huluan kerana anda kini mengembalikan ralat dan bukannya beberapa nilai lalai.

Pilihan 2 - Pemfaktoran semula aliran reaktif yang betul

Lebih baik mengendalikan ralat di dalam api aliran reaktif

  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. Pengecualian menelan - dengan aliran reaktif

Corak daripada contoh satu juga muncul dalam strim reaktif:

  public Mono<String> getHello() {
    return helloRepository.getHello()
        .map("Hello %s"::formatted)
        // do nothing on error, just return a default value
        .onErrorReturn("Hello world");
  }

Itu kelihatan sangat bagus dan bersih, bukan? Tetapi kami tidak akan dapat mengesan bahawa ralat dilemparkan ke dalam repositori!
Jika terdapat nilai lalai, sekurang-kurangnya log ralat harus ditulis.

Mitigasi

Seperti dalam contoh sebelumnya, kami membungkus pengecualian dalam pengecualian lain, kali ini dalam pengecualian tersuai yang menjadikannya lebih mudah untuk mengesan tempat khusus di mana pengecualian itu dilemparkan

  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. Log berulang kali surih tindanan untuk pengecualian yang sama

Kebalikan daripada menggugurkan pengecualian secara senyap ialah mengelog pengecualian yang sama beberapa kali. Ini adalah amalan buruk kerana ia mengelirukan log dengan ratusan atau ribuan baris surih tindanan tanpa memberikan sebarang makna tambahan.

Dalam contoh terburuk saya, saya menemui surih tindanan yang sama lima kali dalam log tanpa mesej bermakna langsung.

Contoh Buruk:

Pengawal:

@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);
        });
  }
}

Dan dalam Perkhidmatan:

@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);
        });
  }
}

... dan mungkin di beberapa tempat lagi...

Mitigasi

Ini diterangkan dalam artikel saya sebelum ini, 3-step-error-handling, oleh itu saya tidak akan menunjukkan kod di sini lagi, melainkan cadangan:

  • Mempunyai pengendali ralat global yang merekodkan ralat dan mengembalikan mesej ralat yang betul kepada pengguna.
  • Dalam kod, elakkan jejak tindanan tetapi
    • balut pengecualian dalam pengecualian tersuai atau pengecualian masa jalan dengan mesej yang bermakna
    • log mesej ralat (bukan keseluruhan surih tindanan)
    • log surih tindanan hanya dalam pengendali ralat global

3. Tangkap pengecualian generik

Menangkap pengecualian generik seperti Exception atau Throwable boleh menyebabkan tingkah laku yang tidak diingini dan menyukarkan penyahpepijatan. Adalah lebih baik untuk menangkap pengecualian khusus.

Contoh Buruk

  public Mono<String> getHello() {
    try {
      return helloRepository.getHello();
    } catch (Exception e) {
      log.error("Error while fetching hello data", e);
      return Mono.empty();
    }
  }

Mitigasi

Tangkap pengecualian khusus untuk mengendalikan senario ralat yang berbeza dengan sewajarnya.

  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));
    }
  }

dan yang setara menggunakan api strim reaktif

  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. (Lebih-)menggunakan pengecualian yang ditandakan

Pada pendapat saya, pengecualian yang diperiksa di mana kesilapan dalam Java, ia tidak begitu berguna dan sering membawa kepada amalan buruk dan kod yang bersepah.

Contoh Buruk

public void someMethod() throws IOException, SQLException {
  // some code that might throw an exception
}

Dengan pengecualian yang ditandai, anda perlu berurusan dengannya dalam kod panggilan, yang menjadikannya lebih sukar untuk menggunakan corak lain, mis. pengaturcaraan berfungsi atau aliran reaktif atau pada musim bunga pengendali ralat global.

Pengecualian:
Pengecualian yang disemak berguna cth. dalam kod perpustakaan, di mana anda mahu memaksa pengguna mengendalikan pengecualian.

Mitigasi

Gunakan pengecualian yang tidak ditandai untuk senario yang pemanggil tidak boleh dijangka pulih secara munasabah.

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.

Atas ialah kandungan terperinci Ralat Mengendalikan Amalan Buruk. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel sebelumnya:API CRUD REHATArtikel seterusnya:API CRUD REHAT