This repository was archived by the owner on Dec 30, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
Chapter 07. Error Handling
YoungMinKim edited this page Sep 11, 2021
·
25 revisions
2021.09.04 (SAT) 10:20-12:00 (100mins)
🚀 Lead by. 'YoungMin'
오류 처리는 프로그램에 반드시 필요한 요소 중 하나:
- 상당수 코드 기반은 전적으로 "오류 처리 코드"에 "좌우"되기 때문에 "깨끗한 코드"와 연관이 있다.
- 여기저기 흩어진 오류 처리 코드 때문에 실제 코드가 하는 일을 파악하기 힘들어진다.
- 오류 처리 코드로 인해 프로그램 논리를 이해하기 어려워진다면 깨끗한 코드라 부르기 어렵다.들어가기에 앞서 오류와 예외에 대해 간략히 알아보자:
- 오류 그리고 예외란 무엇인가?
- 오류와 예외는 어떤 차이가 있는가? - 오류란 시스템에 비정상적인 상황이 생겨 해당 시스템(Process)이 종료되어야 할 수준의 수습할 수 없는 심각한 문제.
- 시스템 레벨에서 발생하기에 개발자가 미리 예측할 수 없는 심각한 오류.
- 예외란 개발자가 구현한 로직에서 발생한 실수 혹은 사용자의 행동에 의해 발생.
- 오류와 달리 개발자가 미리 예측하여 방지할 수 있기에 상황에 맞는 예외처리(Exception Handle)를 해야한다.
왜 오류 코드보다 예외 사용을 지향해야 하는가?:
- 얼마 전까지만 해도 예외를 지원하지 않는 프로그래밍 언어가 많았다.
- 예외를 지원하지 않는 언어는 "오류를 처리하고 보고하는 방법이 제한적이다."
- "오류 플래그" 설정
- 호출자에게 "오류 코드" 반환// Bad
public class DeviceController {
...
public void sendShutDown() {
DeviceHandle handle = getHandle(DEV1); // Check Device Status
if (handle != DeviceHand.INVALID) {
retrieveDeviceRecord(handle); // Save device status to record field
if (record.getStatus() != DEVICE_SUSPENDED) { // Shut down if device is not paused
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
} else {
logger.log("Device suspended. Unable to shut down");
}
} else {
logger.log("Invalid handle for: " + DEV1.toString());
}
}
}- 위와 같은 방법은 함수를 호출한 즉시 오류를 확인해야 하기에, 호출자 코드가 복잡해진다.
-
오류가 발생하면 예외를 던지는 편이 낫다.
- 호출자 코드가 깔끔해진다.
- 코드의 논리 영역와 오류 처리 코드를 분리할 수 있다.
// Good
public class DeviceController {
...
public void sendShutDown() {
try {
tryToShutDown(); // Simplified call statement
} catch (DeviceShutDownError e) {
logger.log(e);
}
}
private void tryToShutDown() throws DeviceShutDownError {
DeviceHandle handle = getHandle(DEV1); // Check Device Status
DeviceRecord record = retrieveDeviceRecord(handle);
pauseDevice(handle);
clearDeviceWorkQueue(handle);
closeDevice(handle);
}
private DeviceHandle getHandle(DeviceId id) {
...
throw new DeviceShutDownError("Invalid handle for: " + id.toString());
...
}
}- 위 코드를 보면 앞서 뒤 섞였던 개념을 분리 하였다.
- 디바이스의 상태를 파악하여 저장 후 종료하는 알고리즘.
- 오류를 처리하는 알고리즘.
- 각 개념을 독립적으로 살펴보고 이해할 수 있다.
- 코드의 품질과 가독성이 높아졌다.
throw와 throws의 차이?:
- throws와 throw는 둘 다 예외(Exception)을 발생 시킨다는 것에는 차이가 없다.
- 하지만 두 키워드 사이에는 차이점이 존재한다.class CheckedException extends Exception {
public CheckedException(String message) {
super(message);
}
}
class UnCheckedException extends RuntimeException {
public UnCheckedException(String message) {
super(message);
}
}class Student {
...
public void callStudent() {
try {
if (null == this.name || null == this.age) {
throw new CheckedException("An exception has occurred in callStudent method");
}
System.out.println("이름 = " + this.name + ", 나이 = " + this.age);
} catch (CheckedException e) {
e.printStackTrace();
}
}
}
public class HandleException {
public static void main(String[] args) {
Student st = new Student("kym", "29");
st.callStudent();
st = new Student(null, "23");
st.callStudent();
}
}-
throw는 메서드 내에서 예외 처리 하기 위해, 개발자가 예외(Exception)를 강제로 발생시키는 것. - 애플리케이션이 예외를 적절히 처리하지 못하면 프로그램이 죽거나 오작동하게 된다.
public class ThrowsTest {
public static void main(String[] args) {
System.out.println("====Throws TEST====");
method1();
}
public static void method1() {
try {
method2();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void method2() throws ClassNotFoundException {
// ok : java.lang.String
Class clazz = Class.forName("java.lang.StringError"); // error!
}
}- 현재 메서드를 기준으로 자신을 호출한 상위 메서드로 Exception을 발생 시킨다.
-
throws키워드를 사용하는 메서드를 호출한 상위 메서드에게 에러 처리에 대한 책임을 전가한다.
Try-Catch-Finally?:
- 예외가 발생할 것 같은 코드를 짤 때는 try-catch-finally 문으로 시작하는 편이 낫다.
- try 블록에 들어간 코드 실행 시, "어느 시점에서든 실행 중단 후 catch 블록으로 넘어갈 수 있다".
- try 블록에서 무슨 일이 생기든 catch 블록은 프로그램 상태를 "일관성 있게 유지"해야 한다.- 즉, try-catch-finally 구문을 선언함으로써 무슨 일이 생기든 호출자가 기대하는 상태 정의가 가능해진다.
// 단위 테스트를 위해 @Test 어노테이션 사용
@Test(expected = StorageException.class)
public void retrieveSectionShouldThrowOnInvalidFileName() {
sectionStore.retrieveSection("invalid - file");
}public List<RecordedGrip> retrieveSection(String sectionName) {
// This Line : 예외 발생을 위해 해당 라인에서의 어떠한 제스처가 필요함
return new ArrayList<RecordedGrip>();
}위 코드는 파일이 없으면 예외를 던지는지 알아보는 단위 테스트를 구현한 코드다.- 예외를 발생시켜야 하는데, 예외가 발생되지 않아 단위 테스트에 실패한다.
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInputStream fis = new FileInputStream(sectionName);
} catch (Exception e) {
throw new StorageException("retrieval error = ", e);
}
return new ArrayList<RecordedGrip>();
}- 파일을 직접 읽어들이는 코드를 작성해 보았다.
- 파일을 읽는 과정에서 예외가 발생하게 되면 해당 예외를 호출자에게 반환하게 되고 단위 테스트 성공하게 된다.
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInputStream fis=new FileInputStream(sectionName);
// 비즈니스 로직 영역
// ..
// ..
// end
fis.close();
} catch (FileNotFoundException e) {
throw new StorageException("retrieval error = ",e);
}
return new ArrayList<RecordedGrip>();
}- catch 블록에서의 기존 예외 유형(Exception e)을 좁혀 리팩토리하는 과정을 거친다.
확인된 예외를 왜 사용해야 하는가?:
- 여러 해 동안 프로그래머들은 "확인된 예외"의 장단점을 놓고 논쟁을 벌여왔다.
- 자바 첫 버전이 확인된 예외를 선보였던 당시는 확인된 예외가 상당히 "멋진 아이디어"로 여겨졌다.
- 하지만 안정적인 소프트웨어를 제작하는 요소로 확인된 예외는 반드시 필요하지 않다는 사실이 분명해졌다.
| Checked Exception | Unchecked Exception | |
|---|---|---|
| 처리 여부 | 반드시 예외 처리를 해야함 |
명시적인 처리를 강제하지 않음 |
| 확인 시점 | 컴파일 단계(Compile) | 실행 단계(Runtime) |
| 예외 발생시 트랜잭션 처리 | Roll-back 안됨 |
Roll-back 함 |
| 대표 Exception | IOException, SQLException | NullPointerException, IllegalArgumentException |
- 위에서 설명하였지만 Checked Exception은 확인된 예외로 컴파일 시점에 확인되는 예외를 말한다.
-
확인된 예외는 OCP 원칙 을 위반한다.
- 메서드에서 확인된 예외를 던졌는데 catch 블록이 세 단계 위에 있다면, 그 사이 메서드 모두가 선언부에 해당 예외를 정의해야 한다.
- 즉, 하위 단계에서 코드를 변경하면 상위 단계 메서드 선언부를 전부 고쳐야 한다는 말이다.
- 모듈과 관련된 코드가 바뀌지 않아도, 선언부가 변경되었기에 다시 빌드 후 배포 해야한다.
class HandleCheckedException {
public void catchFileInputException() {
try {
method1();
} catch (IOException e) {
e.printStackTrace();
}
}
public void method1() throws IOException {
method2();
}
public void method2() throws IOException {
method3();
}
public void method3() throws IOException {
FileInputStream fst = new FileInputStream(new File("/Users/youngminkim/Desktop/Images/naver2.png"));
FileOutputStream fos = null;
int cnt = 0;
while ((cnt = fst.read()) != -1) {
System.out.println("cnt = " + cnt);
break;
}
fst.close();
}
}
public class CheckException {
public static void main(String[] args) {
HandleCheckedException h = new HandleCheckedException();
h.catchFileInputException(); // call method
}
}- 위 코드를 보면 main 메서드 내에서 객체를 생성한 후 파일을 읽어들이는 로직을 작성하였다.
- main -> catchFileInputException -> method1() -> method2() -> method3()
- method3() throws IOException 은 method2() 메서드가 받게 된다.
- method2() 메서드는 해당 예외를
try~catch로 처리하거나, 상위 메서드(자신을 호출한 메서드)에 넘겨야한다.
- 이러한 특징으로 인해 최하단(마지막 호출된 메서드)부터 최상단(최초로 다른 메서드를 호출한 메서드)까지 연쇄 작용이 일어난다.
예외에 의미를 제공하여 의미를 더욱 더 확실히 한다:
- 예외를 던질 때는 "전후 상황"을 덧붙인다. 그러면 오류가 발생한 "원인"과 "위치"를 찾기가 쉽다.- 오류 메시지에 정보를 담아 예외와 함께 던진다.
- 실패한 연산 이름과 실패 유형도 언급한다.
- 애플리케이션이 로깅 기능을 사용 한다면 catch 블록에서 오류를 기록하도록 충분한 정보를 넘겨준다.
// Bad
try {
process(url);
} catch(IOException e) {
// 공란
}
// Bad
try {
process(url);
} catch(IOException e) {
// 단순히 기본 에러 메시지 출력
e.printStackTrace();
}// Good
try {
process(url);
} catch(IOException e) {
log.debug("Fail to call API = " + url, e);
}- process(url) 호출 실패 시,
실패 메시지와실패 정보를 남긴다
오류를 분류하는 방법?:
- 오류를 분류하는 방법은 수 없이 많고, 오류가 발생한 위치를 통해 분류가 가능하다.
- 즉, 오류가 발생한 "컴포넌트", "유형"으로 오류를 분류 할 수 있다.
- 애플리케이션에서 오류를 정의할 때 "가장 중요한 관심사는 오류를 잡아내는 방법"이다. - 예외 클래스(Exception Class)를 만들 때 가장 중요한것은 어떤 방식으로 예외를 잡을까이다.
- 써드파티 라이브러리(Third Party Library)를 사용하는 경우 그것들을 Wrapping 함으로써 라이브러리 교체 등의 변경이 있는 경우 대응이 쉬워진다.
- 호출부에서는 하나의 Exception 만 처리하고 예외 클래스를 만들어 throws 하는 형태로 사용해보자.
// Bad
public class ConnectACMEPortAPI {
public static void main(String[] args) {
ACMEPort port = new ACMEPort(12);
try {
port.open();
} catch (DeviceResponseException e) {
reportPortError(e);
logger.log("Device response exception = ", e);
} catch (ATM1212UnlockedException e) {
reportPortError(e);
logger.log("Unlock exception = ", e);
} catch (GMXError e) {
reportPortError(e);
logger.log("Device response exception = ", e);
} finally {
...
}
}
}- 위 코드를 보면 외부 라이브러리(API)가 던지는 예외를 다 처리하는 구문이다.
- 위 같이 예외 처리를 하게 되면 외부 라이브러리의 API에 종속적인 상태가 되고 코드의 중복이 반복된다.
- 또한 에러마다 기능을 추가해야 한다면 코드가 더욱 더 길어질 것이다.
// Good
// Wrapper Class(Exception Class)로 감싸서 처리
class LocalPort {
private ACMEPort innerPort; // obj
public LocalPort(int portNumber) {
this.innerPort = portNumber;
}
public void open() {
try {
innerPort.open();
} catch (DeviceResponseException e) {
throw new PortDeviceFailure(e);
} catch (ATM1212UnlockedException e) {
throw new PortDeviceFailure(e);
} catch (GMXError e) {
throw new PortDeviceFailure(e);
}
}
...
}
public class ConnectACMEPortAPI {
public static void main(String[] args) {
LocalPort port = new LocalPort(12);
try {
port.open();
} catch (PortDeviceFailedException e) {
reportPortError(e);
logger.log(e.getMessage(), e);
} finally {
...
}
}
}- 실제로 외부 API를 사용할 때는 감싸기 기법이 최선이다.
-
외부 API를 클래스로 감싸면 아래와 같은장점이 생긴다.- 에러 처리 로직이 간결해진다.
- 외부 라이브러리와 프로그램 사이의 의존성이 크게 줄어든다.
- 추후 다른 라이브러리로 갈아타도 비용이 적다.
- Wrapper Class에서 외부 API를 호출하는 대신, 테스트 코드를 넣어주는 방법으로 테스트가 용이해진다.
- 혼히 예외 클래스가 하나만 있어도 충분한 코드가 많다.
- 예외 클래스에 포함된 정보로 오류를 구분해도 괜찮은 경우가 그렇다.
- 위 같은 이유로, 한 예외는 잡아내고 다른 예외는 무시해도 괜찮은 경우 여러 예외 클래스 사용을 지향하자.
앞 절에서 충고한 지침을 통해 비즈니스 논리와 오류 처리가 잘 분리된 코드가 나온다:
- 오류 감지가 프로그램 언저리로 밀려난다.
- 때로는 이러한 중단이 적합하지 않을때가 있다.
- 객체를 조작해 특수사례를 처리하거나, 특수 사례 패턴으로 클래스를 만든다.
- 클라이언트 코드가 예외적인 상황을 처리할 필요가 없어진다.// Bad
public String getTotalExpenses(Employee employee) {
try {
MealExpenses expenses = expensesReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
} catch(MealExpensesNotFound e){
m_total += getMealPerDiem();
}
}// Good
public String getTotalExpenses(Employee employee) {
MealExpenses expenses = expensesReportDAO.getMeals(employee.getID());
m_total += expenses.getTotal();
}public class PerDiemMealExpenses implements MealExpenses {
public int getTotal() {
// 기본값으로 일일 기본 식비를 반환한다.
}
}- 두 번째 코드를 특수 사례 패턴(Special Case Pattern)이라고 부른다.
- 클래스나 객체를 조작하여 특수 사례를 처리하는 방식.
- 클래스나 객체가 예외적인 상황을 캡슐화하여 처리하기 때문에, 클라이언트 코드가 예외적인 상황을 처리할 필요가 없어진다.
무의식적으로 할 수 있는 null 반환:
- 오류 처리를 논하는 장이기에, 우리가 흔히 저지르는 바람에 오류를 유발하는 "행위"도 언급해야 한다 생각한다.
- 애플리케이션 구축 과정에서 우리가 무의식적으로 null을 반환하는 시스템을 구현하는 것을 의미. - 한줄 건너 하나씩 null을 확인하는 코드로 가득한 애플리케이션을 지금까지 수도없이 봤다.
// Bad
public void regsiterItem(Item item) {
if(item != null) {
ItemRegistry registry = peristentStore.getItemRegistry(); // null or data
if(registry != null) {
Item existing = registry.getItem(item.getId()); // null or data
if(existing.getBillingPeriod().hasReatailOwner()) {
existing.register(item);
}
}
}
}- 이런 코드를 기반으로 코드를 짜왔다면 무엇이 문제인지 무엇이 나쁜지 모를수도 있다.
- 일단 위 코드는 나쁜 코드다!
-
왜 나쁜 코드인가?- null을 반환하는 코드는 일거리를 늘릴뿐만 아니라 호출자에게 문제를 떠넘긴다. 좋지 못한 코딩 습관.
- 누구 하나라도 null 확인을 빼먹는다면 애플리케이션이 통제 불능에 빠질지 모른다.
- null을 리턴하고 싶은 생각이 들면
특수 사례 객체를 리턴하라. - ex)
Collections.emptyList()
- null 확인이 누락된 문제이지만, 더 큰 문제는 null 확인이 너무 많다는게 더욱 더 문제다.
- 둘째 행에 null 확인이 빠진 부분.
- 만약 peristentStore.getItemRegistry()가 null을 반환하는 경우.
- 위쪽 어디선가 NullpointException을 잡을지도 모르지만, 어느쪽이든 좋지 않다.
예외를 던진다특수 사례 객체를 반환한다
// Bad
public void addTotalPay() {
int totalPay = 0;
List<Employee> employees = getEmployees();
if(employees != null) {
for(Employee employee : employees) {
totalPay += employee.getPay();
}
}
}// Good
public void addTotalPay() {
int totalPay = 0;
List<Employee> employees = getEmployees();
for(Employee employee : employees) {
totalPay += employee.getPay();
}
}public List<Employee> getEmployees() {
List<Employee> employeeList = employeeDAO.getEmployees(); // Get Data from DB
return (employeeList.size() > 0) ? employeeList : Collection.emptyList();
}-
getEmployees()는null도 반환 한다. 하지만 굳이 null을 반환할 필요가 있는가? -
getEmployees()를 조금 수정하여특수 사례 객체(Collections.emptyList())를 반환한다.
인수로 null을 전달하지 마라:
- 메서드에서 null을 반환하는 방식도 나쁘지만, 메서드로 null을 전달하는 방식은 더 나쁘다.
- 즉, 정상적인 인수로 null을 기대하는 API가 아니라면 메서드로 null을 전달하는 코드는 최대한 피한다.// Basic
public class MetricsCalculator {
public double xProjection(Point p1, Point p2) {
return (p2.x - p1.x) * 1.5;
}
...
}public static void main(String[] args) {
MetricsCalculator calculator = new MetricsCalculator(); // Create Object
calculator.xProjection(null, new Point(12,13)); // Call function xProjection
}- 위 코드처럼 첫 번째 인수 값에 null을 전달한다면 어떻게 될까?
NullPointerException 발생!
// Handle Exception
public class MetricsCalculator {
public double xProjection(Point p1, Point p2) {
if (p1 == null || p2 == null) {
throw InvalidArgumentException("Invalid argument for MetricsCalculator.xProjection"); // Throw Current Exception
}
return (p2.x - p1.x) * 1.5;
}
...
}- 매개변수값
p1,p2값에 대한 유효성 처리 후 예외가 발생하면InvalidArgumentException을 예외로 던진다. - 예외를 상위 호출부에게 던져서 해당 예외를 처리하도록 구현 하였다.
// Use Assert
public class MetricsCalculator {
public double xProjection(Point p1, Point p2) {
assert p1 != null : "p1 should not be null";
assert p2 != null : "p2 should not be null";
return (p2.x - p1.x) * 1.5;
}
...
}-
assert는JDK 1.4부터 지원이되는 객체가 아닌예약어
public class AssertTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int num1 = sc.nextInt();
int num2 = sc.nextInt();
System.out.println("result = " + calculator(num1, num2)); // 결과 출력
}
public static int calculator(int num1, int num2) {
assert num1 > 0 : "a is less than 0";
assert num2 > 0 : "b is less than 0";
return num1 + num2;
}
}- 자바
assert문사용. - 예외가 발생하면
Exception을 발생 시킨다.
Spring Framework 3.2부터 @ControllerAdvice가 나옴:
- 각각의 컨트롤러마다 예외 처리 어노테이션을 사용하여 처리.
- 특정 컨트롤러에 종속되지 않은 전역 예외 처리 로직 작성 가능.@Slf4j
@RestController
@RequestMapping("/api")
public class BoardAPIController {
@Autowired
private BoardService boardService;
@ExceptionHandler(CustomException.class)
@GetMapping("/board/getBoardList")
public Page<BoardDTO> getBoardList(
@PageableDefault(size = 10, sort = "createdDate") Pageable pageable,
@RequestParam(required = false) String title,
@RequestParam(required = false) String content) {
Page<BoardDTO> boardDTO =
StringUtils.isBlank(title) || StringUtils.isBlank(content) ?
boardService.getBoardList(pageable) :
boardService.getBoardList(pageable, title, content);
log.debug("boardDTO = {} ", boardDTO.getSize());
return boardDTO;
}
....
}- 각각의 컨트롤러마다
@ExceptionHandler(CustomException.class)를 사용하여 예외 처리를 수행한다.
@Slf4j
@RestController
@RequestMapping("/api")
public class ApprovalController {
@Autowired
private ApprovalService approvalService;
/**
* 유저 경비 출력
* @return
*/
@ExceptionHandler(CustomException.class)
@GetMapping("/approval/getUserPayment")
public String getUserPayments(...) {
...
}
/**
* 유저 경비 저장
* @return
*/
@ExceptionHandler(RuntimeException.class)
@PutMapping("/approval/savePayment")
public String savePayment(...) {
...
}
...
}- 만약 컨트롤러가 1개가 아닐 경우?
- 각각의 컨트롤러 안에 존재하는 메서드에 일일이 @ExceptionHandler(에러.class)를 작성한다?
ApprovalControllerBoardControllerUserControllerAdminControllerPaymentController
@Getter
@ToString
public class ErrorResponse {
private final LocalDateTime timestamp = LocalDateTime.now();
private final String errorCode;
private final String errorMessage;
@Builder
public ErrorResponse(String errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
}@Getter
@ToString
//@AllArgsConstructor
public enum ErrorCode {
RUNTIME_EXCEPTION(HttpStatus.BAD_REQUEST, "E001"),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "E002"),
;
private final HttpStatus status;
private final String code;
private String message;
ErrorCode(HttpStatus status, String code) {
this.status = status;
this.code = code;
}
ErrorCode(HttpStatus status, String code, String message) {
this.status = status;
this.code = code;
this.message = message;
}
}@Getter
public class CustomException extends RuntimeException {
private ErrorCode error;
public CustomException(ErrorCode error) {
this.error = error;
}
}@Slf4j
@RestControllerAdvice
// public class ApiExceptionAdvice extends ResponseEntityExceptionHandler {
public class ApiExceptionAdvice {
/**
* Handle CommonException
*/
@ExceptionHandler( value = { CustomException.class } )
public ResponseEntity<ErrorResponse> exceptionHandler(HttpServletRequest request, final CustomException e) {
log.debug("🚀 CustomException e.getError().getStatus() = {} ", e.getError().getStatus());
log.debug("🚀 CustomException e.getError().getCode() = {} ", e.getError().getCode());
log.debug("🚀 CustomException e.getError().getMessage() = {} ", e.getError().getMessage());
return ResponseEntity
.status(e.getError().getStatus())
.body(ErrorResponse.builder()
.errorCode(e.getError().getCode())
.errorMessage(e.getError().getMessage())
.build());
}
/**
* Handle RuntimeException
*/
@ExceptionHandler( value = { RuntimeException.class } )
public ResponseEntity<ErrorResponse> exceptionHandler(HttpServletRequest request, final RuntimeException e) {
log.debug("🚀 RuntimeException e.getMessage() = {} ", e.getMessage());
log.debug("ExceptionEnum.RUNTIME_EXCEPTION.getStatus() = {} ", ErrorCode.RUNTIME_EXCEPTION.getStatus());
log.debug("ExceptionEnum.RUNTIME_EXCEPTION.getCode() = {} ", ErrorCode.RUNTIME_EXCEPTION.getCode());
//e.printStackTrace();
return ResponseEntity
.status(ErrorCode.RUNTIME_EXCEPTION.getStatus())
.body(ErrorResponse.builder()
.errorCode(ErrorCode.RUNTIME_EXCEPTION.getCode())
.errorMessage(e.getMessage())
.build());
}
/**
* Handle Exception
*/
@ExceptionHandler( value = { Exception.class } )
public ResponseEntity<ErrorResponse> exceptionHandler(HttpServletRequest request, final Exception e) {
log.debug("🚀 Exception e.getMessage() = {} ", e.getMessage());
log.debug("ExceptionEnum.INTERNAL_SERVER_ERROR.getStatus() = {} ", ErrorCode.INTERNAL_SERVER_ERROR.getStatus());
log.debug("ExceptionEnum.INTERNAL_SERVER_ERROR.getCode() = {} ", ErrorCode.INTERNAL_SERVER_ERROR.getCode());
//e.printStackTrace();
return ResponseEntity
.status(ErrorCode.INTERNAL_SERVER_ERROR.getStatus())
.body(ErrorResponse.builder()
.errorCode(ErrorCode.INTERNAL_SERVER_ERROR.getCode())
.errorMessage(e.getMessage())
.build());
}
}- 스프링 공통 예외 처리 코드.
- 하나의 컨트롤러(@Controller)에서 예외처리 한 부분을 공통 모듈을 생성하여 처리한다.
- 깨끗한 코드는 가독성이 높아야하며 안정성 역시 높아야 한다.
-
오류 처리를 프로그램 논리와 분리. 🌟
- 독자적인 사안으로 고려하면 안정성이 높고 가독성이 좋은 코드 작성이 가능하다.
- 독립적인 추론이 가능해지며 코드 유지보수성이 크게 높아진다.
- 올바른 오류 처리를 통해 코드의 가독성과 애플리케이션의 안정성을 높이기 위해 심여를 기울이자.
참고 자료