이 글은 이전 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"); } }
이 예에서는 일부 사용자가 올바른 안녕하세요 메시지를 받지 못했다고 불평하는 이유가 궁금할 수 있지만 오류가 표시되지 않고 사용자가 혼란스러워하고 있다고 생각할 수도 있습니다.
오류가 발생한 경우 이를 처리하세요. 가능하다면 이미 저장소에 있습니다.
여기에서 처리해야 한다면 최소한 오류를 기록하고 적절한 오류 메시지를 반환하세요.
최소 리팩토링
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!