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.
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:
/** * 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.
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
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.
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)); }
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.
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)); }
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.
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...
Ini diterangkan dalam artikel saya sebelum ini, 3-step-error-handling, oleh itu saya tidak akan menunjukkan kod di sini lagi, melainkan cadangan:
Menangkap pengecualian generik seperti Exception atau Throwable boleh menyebabkan tingkah laku yang tidak diingini dan menyukarkan penyahpepijatan. Adalah lebih baik untuk menangkap pengecualian khusus.
public Mono<String> getHello() { try { return helloRepository.getHello(); } catch (Exception e) { log.error("Error while fetching hello data", e); return Mono.empty(); } }
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)); }
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.
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.
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 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.
Atas ialah kandungan terperinci Ralat Mengendalikan Amalan Buruk. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!