>  기사  >  Java  >  오류 처리 나쁜 관행

오류 처리 나쁜 관행

WBOY
WBOY원래의
2024-09-03 18:33:12578검색

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

이 예에서는 일부 사용자가 올바른 안녕하세요 메시지를 받지 못했다고 불평하는 이유가 궁금할 수 있지만 오류가 표시되지 않고 사용자가 혼란스러워하고 있다고 생각할 수도 있습니다.

옵션 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
이전 기사:REST CRUD API다음 기사:REST CRUD API