반응형
가독성 높이는 습관 - 익셉션 핸들링 (1)
프로그래밍에서 에러
Compile Error
- 컴파일 타임에 발생하는 에러
- syntax (잘못된 문법)
- type (잘못된 타입)
Runtime Error
- 프로그램 실행 중 발생하는 에러
오류(error)와 예외(exception)
Throwable throwable = new Throwable();
- Object → Throwable → Error(unchecked) or Exception(checked)
오류
- 대부분의 시스템에서 비정상적인 상황을 마주했을 때 발생
- OutOfMemoryError : JVM에 할당된 메모리 부족
- StackOverFlowError : 재귀와 같은 특정 상황에 빠져 콜 스택이 꽉찬 경우
- 이러한 에러들은 어플리케이션에서 캐치를 하더라도 처리할 방법이 없음
예외
- 작성된 코드 내에서 발생하므로 예외가 발생했을 때 처리방법을 잘 알고 있어야함
- Exception(checked) → RunTimeException(unchecked) or Other Exceptions(checked)
- RunTimeException : 예외처리를 강제하지 않으며, 런타임에 체크
- ex. NullPointerExcption, IllegalArgumentException, ArithmeticException, IndexOutBoundsException
- Other Exceptions : 호출하는 대상에게 예외처리를 강제하고, 컴파일 타임에 체크 (명시적인 처리 필요)
- ex. ClassNotFoundException, IOException, FileNotFoundException, SQLException, NoSuchMethodException
잘못된 핸들링 - return 값을 통한 에러 핸들링 (1)
// 절대 따라하지 말 것
public void foo() {
int result = otherService.call();
if (result == 0) {
log.info("정상");
// 정상일 때 return 값 작성
} else {
log.info("에러");
// 에러일 때 return 값 작성
}
}
// 절대 따라하지 말 것
public void foo() {
String result = otherService.call();
if (result.equalsIgnoreCase("SUCCESS")) {
log.info("정상");
// 정상일 때 return 값 작성
} else {
log.info("에러");
// 에러일 때 return 값 작성
}
}
// 절대 따라하지 말 것
public void foo() {
ResultCode result = otherService.call();
if (result == ResultCode.SUCCESS) {
log.info("정상");
// 정상일 때 return 값 작성
} else {
log.info("에러");
// 에러일 때 return 값 작성
}
}
잘못된 핸들링 - return 값을 통한 에러 핸들링 (2)
// 절대 따라하지 말 것
public void foo(Parameter parameter) {
Map<String, Object> response = new HashMap<>();
otherService.call(parameter, response);
if (response.get("ERROR") == null) {
log.info("정상");
// 정상일 때 return 값 작성
} else {
log.info("에러");
// 에러일 때 return 값 작성
}
}
// 절대 따라하지 말 것
public void foo() {
Map<String, Object> result = otherService.call();
if (result.get("ERROR") == null) {
log.info("정상");
// 정상일 때 return 값 작성
} else {
log.info("에러");
// 에러일 때 return 값 작성
}
}
// 절대 따라하지 말 것
public void foo() {
SuperDto result = otherService.call();
if (result.getError() == null) {
log.info("정상");
// 정상일 때 return 값 작성
} else {
log.info("에러");
// 에러일 때 return 값 작성
}
}
return을 통한 핸들링의 문제점
- 함수를 호출한 모든 곳이 복잡해짐
- 에러를 처리하는 로직과 기존의 비지니스 로직이 뒤섞여서 하나의 함수가 여러가지 일을 수행
- 유사한 에러로직이 어플리케이션 전반에 중복되게 됨
- 새로 추가된 로직에 대해서 누락하기 쉬움
올바른 핸들링 - throw 활용하기
try {
otherService.call();
// 정상 케이스
} catch (NullPointException | NoSuchElementException e) {
log.error(e.getMessage());
} catch (IndexOutOfBoundsException e) {
log.error(e.getMessage());
} catch (RuntimeException e) {
log.error(e.getMessage());
}
// try-catch 사이에 발생한
// 모든 RuntimeException을 받을 수 있음
void call() {
throw new RuntimeException("어떤 에러가 발생함");
}
올바른 핸들링 - catch 절에서 잡은 익셉션은 반드시 처리하기
try {
otherService.call();
} catch (RuntimeException e) {
e.printStackTrace();
// doNoting
}
- 서버에서 직접 로그를 확인하지 않는 이상 해당 메서드가 정상 동작을 하는지 알 수 없음
- 이러한 코드는 에러 상황을 정상 상황으로 속였기 때문에 에러 추적이 굉장히 어려움
- 특히 스프링 트랜잭션과 뒤섞여 있다면 어플리케이션은 정상적으로 진행했다고 클라이언트에 응답하겠지만,
- 내부적으로는 DB에서 rollback 하는 기이한 현상이 발생하기도 함
올바른 핸들링 - 익셉션은 정말 예외 상황일 때만 사용하기
public String decrypt(String encryptedText) {
try {
return cryptoService.decryptRsa(encryptedText);
} catch (Exception e) {
try {
return cryptoService.decryptAes(encryptedText);
} catch (Exception exception) {
return cryptoService.decryptDes(encryptedText);
}
}
}
올바른 핸들링 - 최상위 Exception 하나로만 잡지 않기
try {
otherService.call();
} catch (Exception e) {
if (e instanceof RuntimeException) {
// doSomething
} else if (e instanceof NoSuchElementException) {
// doSomething
}
}
올바른 핸들링 - call stack 잃어버리지 않기
try {
otehrService.call();
} catch (RuntimeException e) {
throw new ZeroBaseException();
}
올바른 핸들링 - 너무 많은 CustionException 만들지 않기
(생략)
올바른 핸들링 - Checked Exception
public String decrypt(String encryptedText) {
try {
return cryptoService.decryptRsa(encryptedText);
} catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
return ""; // 복구해야하는데 어떻게 하지...
}
}
public User foo(User user) {
try {
return jdbcService.executeQuery("select * from user where user_id = 1");
} catch (SQLException e) {
return null; // 복구해야하는데 어떻게 하지...
}
}
public String decrypt(String encryptedText) throws NoSuchPaddingException, NoSuchAlgorithmException {
return cryptoService.decryptRsa(encryptedText);
}
public User foo(User user) throws SQLException {
return jdbcService.executeQuery("select * from user where user_id = 1");
}
public String decrypt(String encryptedText) {
try {
return cryptoService.decryptRsa(encryptedText);
} catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
throw new ZeroBaseException(e);
}
}
public User foo(User user) {
try {
return jdbcService.executeQuery("select * from user where user_id = 1");
} catch (SQLException e) {
throw new ZeroBaseException(e);
}
}
정리
- Compile Eroor와 Runtime Error 가 존재
- 오류와 예외가 존재
- Checked exception과 Unchecked exception
- 올바른 핸들링
- 1. throw 하기
- 2. 잡은 익셉션은 반드시 처리하기
- 3. 예외 상황일 때만 쓰기
- 4. 최상위에서 exception 한 개만 잡지 않기
- 5. call stack 잃어버리지 않기
- 6. 너무 많은 custom exception은 지양하기
반응형
'cs > java-spring-boot' 카테고리의 다른 글
[Zero-base] 10-3. 테스트 코드 (1) (0) | 2022.03.18 |
---|---|
[Zero-base] 10-2. 익셉션 핸들링 (2) - 실습 (0) | 2022.03.18 |
[Zero-base] 9-15. 주석 (0) | 2022.03.18 |
[Zero-base] 9-14. 심미적 방법들 (0) | 2022.03.17 |
[Zero-base] 9-13. 심플하게 구성하기 (0) | 2022.03.17 |