컴파일 에러와 런타임 에러 (Checked / Unchecked Exception)
java는 에러를 자바 프로그램 입장에서의 에러 발생 근원 (source)에 따라 2가지로 구분해 놓음
- 자바 프로그램 "외부" 에서 발생하는 것: (Need to be) Checked Exception
- 충분히 예상 가능한 에러이기에, 복구가 필요한 예외(예외 처리 강제) + 제어할 수 있다.
- 자바 프로그램 "내부 로직" 에서 발생하는 것: Unchecked Exception
- 어떤 상황에서 발생되는지 예측할 수 없어, 어떻게 복구해야할지 전혀 알 수 없다.
- 그래서 로그를 잘 남겨야한다. 그게 Unchecked Exception 처리의 가장 근본, 원칙
에러 처리: Try -> Throw -> Catch
자바 언어에서는 Try -> Throw -> Catch의 구성으로 에러를 처리
이 빨간색 에러를 해결하려면, Java Exception을 처리하면 된다 (예외 처리하는 방법 2가지)
- Try-Catch: 메서드 내에서 직접 처리
- Throws: 넘겨서 다른 메서드에서 처리하도록 이관
에러 처리: Exception 예외던지기(Throw) + 예외 받기 (Try-Catch)
exit code
예외가 발생하하면 터미널에 아래 사진과 같이 exit code 0 | 1 이 뜰 것이다.
- `exit code 1`: 비정상 종료
- `exit code 0`: 정상 종료
✅ Unchecked Exception 처리
런타임에 발생하는 Exception, 런타임 시간에 에러 처리
- 자바 프로그램 동작 시 로직에서 에러가 발생하면, 왜 발생했는지 로그를 꼭 표기하여, 어디서 문제가 발생했는지 추론 혹은 디버깅할 수 있게 한다.
- Stacktrace: 어디서 발생했습니까? = Call Stack
우리가 예상하지 못한 예외이므로 미처 처리하지 못한 예외 -> IDE가 알려주지 못함
- 예외 처리 했는지 여부를 컴파일 시간에 검증: 개발 시 IDE에서 빨간색으로 표기
일반적으로 많이 쓰이는 Unchecked Exception
- ArithmeticException: 나눌때 사용할 값이 변수라면, +- 등 모종의 이유로 0이 될 수 있다.
- ArrayIndexOutOfBoundsException: 배열의 길이가 모종의 이유로 바뀌어 접근 불가능할 수 있다.
- RuntimeException: Java 개발 시 가장 많이 사용 되는 Unchecked Exception
자바를 잘하는 개발자는 (Runtime) Exception 처리를 잘하는 개발자이다
- Exception 처리를 잘한다는 것 = 매 로직에서 발생할만한 에러, 상황들을 예지할 수 있단 뜻
- Exception 처리를 잘하는 개발자란
- 상황 "범주" 에 맞춰 Custom Exception 정의
- 적합한 곳에서, 어떤 조건에 적합한 Custom Exception 발행(Throw 최초 발행)
- 중간 어딘가에서 적합한 처리 2가지: Throws vs Try-Catch
Custom Exception
- Exception 클래스 만들기 -> 클래스 다중화
- 예외 상황별 Exception 필드값 활용 -> 단일 Exception 클래스
- Exception 우선순위에 따라 로깅 레벨 다중화
- 로깅을 사용하는 이유가 레벨 다중화 할 수 있어서이다.
- https://github.com/xxyeon/spring_inflearn/commit/36e4ed49fa79a945be7d4cebf61aa581e52508d1
- 여러 계층서 Exception 발생 시 중간에 받아서 다시 넘기기
- Exception 타입 변경: 내부 개발자들에게 로그 표기 및 외부 사용자에게 다듬어진 에러 반환
- https://github.com/xxyeon/spring_inflearn/commit/24199e752b38f10218ecb0b356225fa35741d95d
- 여러 계층서 Exception 발생 시 중간에 받아서 처리하기
- 의도적 먹어버림: 에러 발생은 내부에서 처리하고, 로직을 중간에 멈추지 않고 계속 수행하도록
- https://github.com/xxyeon/spring_inflearn/commit/5e8283c0eab8dc1949225dbfbe6ddeddb3bbd500
✅ Checked Exception 처리
컴파일시 발생하는 Exception
실행 전에 무조건 처리해 줘야하는 예외이다.
- 데이터베이스 유저네임과 비번이 틀렸다는 예외는 어플리케이션을 구동해야 알 수 있는 에러임
Checked Exception 처리 방법 1. 넘기기 (서버 오류 발생)
main으로 예외가 넘어오고, main에서 예외 던지기 -> 우리한테 예외가 온다.
- exit code 1 이 뜬다
package com.example.demo;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.sql.SQLException;
@SpringBootApplication
public class DemoApplication {
static void connect(String username, String password) throws SQLException {
if (username.equals("admin") && password.equals("1234")) {
System.out.println("- Database is connected successfully.");
} else {
throw new SQLException("데이터베이스 접속 실패");
}
}
public static void main(String[] args) throws SQLException {
connect("admin", "1234");
connect("user", "9876");
}
}
Checked Exception 처리 방법 2. 받기 (서버 오류 미발생)
package com.example.demo;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.sql.SQLException;
@SpringBootApplication
public class DemoApplication {
static void connect(String username, String password) {
try {
if (username.equals("admin") && password.equals("1234")) {
System.out.println("- Database is connected successfully.");
} else {
throw new SQLException("데이터베이스 접속 실패");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
connect("admin", "1234");
connect("user", "9876");
}
}
⭐️ Throwable.printStackTrace() 사용 지양
.getStackTrace() 활용
package com.example.demo;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.sql.SQLException;
@SpringBootApplication
public class DemoApplication {
static void connect(String username, String password) {
try {
if (username.equals("admin") && password.equals("1234")) {
System.out.println("- Database is connected successfully.");
} else {
throw new SQLException("데이터베이스 접속 실패");
}
} catch (SQLException e) {
System.out.println(e.getMessage());
System.out.println(e.getStackTrace()[0]);
System.out.println(e.getStackTrace()[1]);
}
}
public static void main(String[] args) {
connect("admin", "1234");
connect("user", "9876");
}
}
Exception 처리를 위해 쓰는 `Throwable.printStackTrace()` 사용 관련 안내
PMD서 `e.printStackTrace()` 구문을 Avoid Print Stack Trace 로 삭제 -> Logger로 대체
- `java.lang.System.out`, `java.lang.System.err`을 이용한 로깅은 로그 레벨을 관리할 수 없기 때문에 문제가 된다.
- Exception의 stack trace를 로길할 때도 `ex.printStackTrace()`를 이용하면 안됨
- `ex.printStackTrace()`은 내부적으로 `java.lang.System.err`를 이용해서 로그를 남긴다.
- Exception의 stack trace를 로길할 때도 `ex.printStackTrace()`를 이용하면 안됨
- 자바 어플리케이션 정적 분석을 통해 잘못된 문법 사용에 대해 경고 및 사용자화 가능
- PMD는 잘못된 코드 부분이나 위반 사항을 지적해준다.
- 하지만 PMD에 의해 보고된 문제는 다소 비효율적인 코드일 수 있고, 잘못된 프로그래밍 습관으로 이후에 프로그램의 성능과 유지 보수성을 저하시킬 수 있다.
PMD?
PMD는 확장 가능한 다중 언어 정적 코드 분석기입니다. 사용되지 않는 변수, 빈 캐치 블록, 불필요한 객체 생성 등과 같은 일반적인 프로그래밍 결함을 찾습니다.
PMD는 올바른 형식의 소스 파일만 처리할 수 있기 때문에 컴파일 오류를 보고하지 않습니다.
쓰지말아야하는 이유는 내부적으로 `System.err` PrintStream을 사용하는데 (출처)
- 이 Error 표기를 위한 OutPut Stream 자체가 파일 디스크립터(보안 취약)을 사용하고
- Synchronization 관련 메커니즘이 없기 때문에 멀티스레드에 취약(Not Tread-Safe)
대신 `e.getStackTraece()`를 사용하면 된다 -> Collcetion 타입이라 순서대로 스택이 쌓여있다.
- StackTraceElement 배열 중 첫번째가 가장 마지막 호출지 `e.getStackTrace()[0]`
Slf4j log.error() 활용
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.sql.SQLException;
@Slf4j
@SpringBootApplication
public class DemoApplication {
static void connect(String username, String password) {
try {
if (username.equals("admin") && password.equals("1234")) {
System.out.println("- Database is connected successfully.");
} else {
throw new SQLException("데이터베이스 접속 실패");
}
} catch (SQLException e) {
log.error(String.format("데이터베이스 계정 불일치 - username: %s, password: %s", username, password), e);
}
}
public static void main(String[] args) {
connect("admin", "1234");
connect("user", "9876");
}
}
`System.err`는 파일 디스크립터 보안, Log Rotation, 멀티스레드 취약 문제가 있다.
따라서 `Slf4j log.error()`를 사용하자.
SLF4J(Simple Logging Facade for Java) <- `System.err`와 `System.log` 대체
일반적으로는 SLF4J을 사용한다.
- Logback, Log4j와 같은 Logging Framework 들에 대한 Facade(추상체) 제공
- Logging Framework 변경에도 아무런 문제 없이 로깅 수행
- SLF4J는 Throwable 객체를 두번째 파라미터로 넘기면 Stack Trace를 로깅해준다. (두번재 파라미터가 Throwable이어야한다.)
SLF4J Logger 사용법 & 잘못된 사용법에 대한 글
Exception 잡아 먹어버리는 코드
Stacktrace가 없어서 추적 불가능
Exception을 Catch하는건 좋은데, 먹어버리지마라
Unchecked Exception(RuntimeException)은 원인 분석을 위한 단서가 정말 꼭 필요함.
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.sql.SQLException;
@Slf4j
@SpringBootApplication
public class DemoApplication {
static void connect(String username, String password) {
try {
if (username.equals("admin") && password.equals("1234")) {
System.out.println("- Database is connected successfully.");
} else {
throw new SQLException("데이터베이스 접속 실패");
}
} catch (SQLException e) {
//System.out.println(e.getMessage());
//System.out.println(e.getStackTrace()[0]);
//System.out.println(e.getStackTrace()[1]);
log.error(String.format("데이터베이스 계정 불일치 - username: %s, password: %s", username, password));
}
}
public static void main(String[] args) {
connect("admin", "1234");
connect("user", "9876");
}
}
현업에서 일반적인 사용 시 다양한 계층의 클래스에서 Exception 발생 및 Throw 와 Catch
서비스에서 발행하는 다양한 Exception을 한 데 모아 단일 클래스(CustomException)로 만들어 CustomException만 반환.
예를 들어서 여행 패키지 예약을 위한 항공권, 리조트, 호텔 예약에서 발생하는 Exception을 CustomeException 이라는 대표 Exception(예약 실패)만 고객에게 보여준다.
현업에서 일반적인 객체 사용시 객체의 완전성(객체 생성 후 메소드 호출보장)을 위해 생성자를 통해 초기화 한다. (https://github.com/xxyeon/spring_inflearn/commit/845b4370adcc2434f22892860836583b0db9212e)
'ASAC 웹 풀스택' 카테고리의 다른 글
[Git] 파일 수정 시나리오: 하나의 파일에 기능이 여러개 추가된 경우 (0) | 2024.10.28 |
---|---|
Java 기본 문법 및 JVM 구성(4) - JVM (0) | 2024.09.28 |
Java 기본 문법 및 JVM 구성(2) - 자바 개발 환경 설정(Intellij, Gradle, Lombok) (2) | 2024.09.25 |
Java 기본 문법 및 JVM 구성(1) - Java동작 원리 (0) | 2024.09.25 |
React 의 특장점, 렌더 라이프사이클 및 Hook(7) - immer(리렌더링 이슈 해결) (1) | 2024.09.22 |