핵심 요약
- 스택 추적은 예외 발생 시점의 메소두 호출 정보를 제공하여 디버깅을 돕는다.
- 예외 메시지에는 관련 매개변수와 필드 정보를 포함하여 구체적인 실패 원인을 알 수 있게 해야 한다.
- 예외 클래스를 확장하고, 필요한 상세 정보를 얻기 위한 접근자 메소드를 제공하는 것이 좋다.
스택 추적(Stack Trace)
- 프로그램이 실행되면서 메소드 호출이 일어날 때마다 해당 메소드의 정보가 스택에 쌓인다.
- 이때, 프로그램에서 예외가 발생하면 그 시점의 호출 스택을 표시하는 정보를 "스택 추적"이라고 한다.
- ex. 예외가 발생한 순간의 메소드 호출 순서, 위치 정보, 관련 파일명 등
- 예외가 발생하고 해당 예외를 잡아내지 못하면, JVM은 그 예외의 스택 추적 정보를 자동으로 콘솔에 출력한다.
- 예외 객체의 toString 메소드를 호출하여 얻은 문자열을 기반으로 한다.
- 형식: 예외 클래스 이름 출력 + 상세 메시지
예시 1
public static void main(String[] args) {
String str = null;
System.out.println(str.length()); // NullPointerException 발생
}
Exception in thread "main" java.lang.NullPointerException
at com.example.Main.main(Main.java:3)
실패 순간을 포착하려면 발생한 예외에 관여된 모든 매개변수와 필드의 값을 실패 메시지에 담아야 한다.
- cf. 상세 메시지에 보안과 관련된 정보는 담아서는 안 된다.
- 관련 데이터를 모두 담아야 하지만 장황할 필요는 없다. 즉, 문서와 소스코드에서 얻을 수 있는 정보는 길게 작성할 필요가 없다.
- 예외의 상세 메시지와 최종 사용자에게 보여줄 오류 메시지를 혼동해서는 안 된다.
- 예외의 생성자에서 필요한 정보를 모두 받아서 상세 메시지를 미리 생성하는 것도 좋은 방법이다.
- 예외를 사용하는 곳마다 상세 메시지를 생성하는 로직을 반복해서 작성하지 않아도 되기 때문이다.
예시 2 - IndexOutOfBoundsException
/**
* IndexOutOfBoundsException을 생성한다.
*
* @param lowerBound 인덱스의 최솟값
* @param upperBound 인덱스의 최댓값 + 1
* @param index 인덱스의 실젯값
*/
public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
// 실패를 포착하는 상세 메시지를 생성한다.
super(String.format("최솟값: %d, 최댓값: %d, 인덱스: %d", lowerBound, upperBound, index));
// 프로그램에서 이용할 수 있도록 실패 정보를 저장해둔다.
this.lowerBound = lowerBound;
this.upperBoudn = upperBound;
this.index = index;
}
- IndexOutOfBoundsException의 새로운 생성자는 인덱스의 최솟값, 최댓값, 인덱스 값을 받는다.
- 이 생성자는 전달된 값을 이용하여 상세한 메시지를 생성하고, 해당 정보를 내부 필드에 저장한다.
- 자바 9에서는 IndexOutOfBoundsException에 정수 인덱스 값을 받는 생성자가 추가되었다.
- 그러나 최솟값과 최댓값까지 받는 생성자는 추가되지 않았다.
// 상세 메시지 생성이 예외 생성자에 없는 경우
try {
// ...
} catch(IndexOutOfBoundsException e) {
throw new IndexOutOfBoundsException("최솟값: " + lowerBound + ", 최댓값: " + upperBound + ", 인덱스: " + index);
}
// 상세 메시지 생성이 예외 생성자에 포함된 경우
public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
super(String.format("최솟값: %d, 최댓값: %d, 인덱스: %d", lowerBound, upperBound, index));
}
try {
// ...
} catch(IndexOutOfBoundsException e) {
throw new IndexOutOfBoundsException(lowerBound, upperBound, index);
}
- 상세 메시지 생성 로직이 예외 클래스 내부에 숨겨져 있어, 코드 중복이 줄어들게 된다.
- 물론 검사 예외에 비해 비검사 예외에서는 중요도가 떨어지긴 하지만, 상세 정보를 알려주는 접근자 메소드를 사용하는 것이 좋다.
예시 3
public class DetailedIndexOutOfBoundsException extends IndexOutOfBoundsException {
private final int lowerBound;
private final int upperBound;
private final int index;
public DetailedIndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
super(String.format("최솟값: %d, 최댓값: %d, 인덱스: %d", lowerBound, upperBound, index));
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.index = index;
}
// 접근자 메소드들
public int getLowerBound() {
return lowerBound;
}
public int getUpperBound() {
return upperBound;
}
public int getIndex() {
return index;
}
}
try {
// ...
} catch(DetailedIndexOutOfBoundsException e) {
System.out.println("실제 인덱스 값: " + e.getIndex());
// 추가적인 처리
}
추가 내용
[Spring] 스프링의 다양한 예외 처리 방법(ExceptionHandler, ControllerAdvice 등) 완벽하게 이해하기 - (1/2)
예외 처리는 애플리케이션을 만드는데 매우 중요한 부분을 차지한다. Spring 프레임워크는 매우 다양한 에러 처리 방법을 제공하는데, 어떠한 방법들이 있고 가장 좋은 방법(Best Practice)은 무엇인
mangkyu.tistory.com
[Spring] @RestControllerAdvice를 이용한 Spring 예외 처리 방법 - (2/2)
예외 처리는 robust한 애플리케이션을 만드는데 매우 중요한 부분을 차지한다. Spring 프레임워크는 매우 다양한 에러 처리 방법을 제공하는데, 앞선 포스팅에서 @RestControllerAdvice를 사용해야 하는
mangkyu.tistory.com
예시 4
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Product {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Double price;
@Builder
public Product(String name, Double price) {
this.name = name;
this.price = price;
}
// ... 기타 메서드 및 필드 ...
}
@Data
@Builder
public class ProductDto {
private Long id;
private String name;
private Double price;
// ... 필요한 필드 추가 가능 ...
}
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public ProductDto findProductById(Long productId) {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new ProductNotFoundException("Product with ID " + productId + " not found."));
return convertToDto(product);
}
private ProductDto convertToDto(Product product) {
return ProductDto.builder()
.id(product.getId())
.name(product.getName())
.price(product.getPrice())
.build();
}
public ProductDto createProduct(String name, Double price) {
Product product = Product.builder()
.name(name)
.price(price)
.build();
productRepository.save(product);
return convertToDto(product);
}
}
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/{productId}")
public ResponseEntity<ProductDto> getProduct(@PathVariable Long productId) {
ProductDto product = productService.findProductById(productId);
return new ResponseEntity<>(product, HttpStatus.OK);
}
@PostMapping("/")
public ResponseEntity<ProductDto> createProduct(@RequestBody ProductDto productDto) {
ProductDto createdProduct = productService.createProduct(productDto.getName(), productDto.getPrice());
return new ResponseEntity<>(createdProduct, HttpStatus.CREATED);
}
}
public class ProductNotFoundException extends RuntimeException {
public ProductNotFoundException(String message) {
super(message);
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ProductNotFoundException.class)
public ResponseEntity<String> handleProductNotFoundException(ProductNotFoundException ex) {
return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
// ... 다른 예외 핸들러 메서드들 ...
}
- ProductNotFoundException이 발생할 때 어떤 상품ID로 인해 발생했는지 그 정보를 메시지에 포함시켰다.
- 또한 해당 예제에서는 GlobalExceptionHandler에서 해당 예외를 핸들링할 때 이 정보를 클라이언트에게 전달한다.
'Language > Java' 카테고리의 다른 글
[effective java] 아이템 77. 예외를 무시하지 말라. (0) | 2023.08.12 |
---|---|
[effective java] 아이템 76. 가능한 한 실패 원자적으로 만들라. (0) | 2023.08.12 |
[effective java] 아이템 74. 메서드가 던지는 모든 예외를 문서화하라. (0) | 2023.08.12 |
[effective java] 아이템 73. 추상화 수준에 맞는 예외를 던지라. (0) | 2023.08.12 |
[effective java] 아이템 72. 표준 예외를 사용하라. (0) | 2023.07.31 |