Jake Seo
제이크서 개발 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (719)
    • AI 서비스 개발 일기 (3)
    • LLM 개발 일기 (1)
    • ------레거시 (2025.08.23 이전)--.. (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 싱글톤
  • 메이븐 라이프사이클
  • bean Validation
  • 작업기억공간
  • prerendering
  • pnpm
  • Pre-rendering
  • 외래키 제약조건
  • 팩터리 메서드 패턴
  • 자바
  • 빈 검증
  • 디자인패턴
  • 프로그래머의 뇌
  • 객체복사
  • 메이븐 골
  • item7
  • 참조 해제
  • NEXT JS
  • 스프링 검증
  • 느린 쿼리
  • 싱글턴
  • 토비의 스프링
  • item8
  • 메이븐 페이즈
  • 도커공식문서
  • 서버리스 컴퓨팅
  • Next.js
  • 자바 디자인패턴
  • try-with-resources
  • next js app
  • 자바스크립트 면접
  • 이펙티브 자바
  • Java
  • 싱글톤 패턴
  • item9
  • MSSQL
  • 이펙티브자바
  • 이펙티브 자바 item9
  • 자바 검증
  • rust
  • 플라이웨이트패턴
  • 자료구조
  • 러스트
  • 자바스크립트
  • serverless computing
  • 슬로우 쿼리
  • 추상 팩터리 패턴
  • Javadoc 자바독 자바주석 주석 Comment
  • 알고리즘
  • 자바스크립트 인터뷰

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 개발 블로그

프레임워크/토비의 스프링

토비의 스프링 4장 요약 정리 - 예외 처리

2022. 6. 21. 08:41

예외처리의 함정

예외처리는 코딩 초보 시절에는 왜 하는지도 모르고 그냥 넘어가기 쉽다. 그런데, 예외처리를 대충한다면, 막상 예외가 발생했을 때 디버그가 매우매우 어려워지는 상황이 발생할 수 있다.

가장 문제인 코드

try {
  ...
} catch(Exception e) {
  // no code
}
  • catch 블록에 아무것도 적지 않는 개발자가 많다.
  • 예외 발생 시 무엇이 문제인지도 모른채 코드는 정상적으로 실행되지 않는 상태가 될 수 있다.
    • 소위 예외 블랙홀로 불리며 모든 예외를 잡아먹는다.

덜 문제인 코드

try {
  ...
} catch(Exception e) {
  e.printStackTrace();
}
  • 예외가 무엇인지 적어도 프린트라도 하는 코드이다.

올바른 예외처리 방법은?

크게 복구하는 방법과 단순히 알리기만 하는 방법으로 나누어진다.

  • 복구하는 방법
    • 예외 상황을 복구한다.
    • ex) 몇 초, 몇 분뒤 다시 시도한다.
  • 알리기만 하는 방법
    • 운영자 혹은 개발자에게 어떤 에러가 발생했다고 명확히 알린다.
      • 메일, 문자, 슬랙 알림 등 다양한 방법이 있다.

생각해봐야 할 코드

public void method1() throws Exception {
  method2();
  ...
}

public void method2() throws Exception {
  method3();
  ...
}

public void method3() throws Exception ...
  • 아무 생각없이 메서드 시그니쳐에 Exception만 붙인다.
  • 예외 블랙홀보단 낫지만 이 메서드를 호출하는 상위 스택이 전부 더러워진다.
    • 하위 메서드에서 Exception 을 던지면 하위 메서드를 호출하는 상위 메서드도 강제로 Exception 을 던져주어야 한다.

체크 예외와 언체크 예외

예외는 크게 체크 예외와 언체크 예외로 나눌 수 있다.

체크 예외의 특징

  • try ... catch 문 작성을 강요한다.
  • RuntimeException을 상속하지 않는다.

언체크 예외의 특징

  • try ... catch 문 작성을 강요하지 않는다.
  • RuntimeException을 상속한다.

안정성을 위해 예외 처리를 강제했더니 예외 블랙홀 같은 코드가 나오는 부작용이 생겨버렸다. 요즘 나오는 라이브러리는 오히려 언체크 예외가 더 많이 쓰인다고 한다.

예외처리 방법 정리

예외 복구, 예외처리 회피, 예외 전환이 있다.

예외 복구

  • 어떻게든 정상 상태로 돌려놓는 것이다.
  • ex) 몇 초 뒤 다시 시도 혹은 다른 방법으로 시도하라고 안내한다.
int maxRetry = MAX_RETRY;

while(maxRetry --> 0) {
  try {
    ... // 예외가 발생할 수 있는 시도
    return; // 작업 성공
  }
  catch(SomeException e) {
    // 로그 출력, 정해진 시간만큼 대기
  }
  finally {
    // 리소스 반납, 정리 작업
  }
}

throw new RetryFailedException(); // 최대 재시도 횟수를 넘기면 직접 예외 발생

예외처리 회피

  • 메서드 시그니쳐 뒤에 붙는 throws Exception을 통해 예외처리를 이 메서드를 호출한 곳으로 넘기는 것이다.
  • 이 메서드를 호출한 곳에서 예외를 처리하는 것이 더 낫다는 분명한 근거가 있을 때 사용해야 한다.

빈 try ... catch 코드를 작성하는 것과는 다름에 유의하자.

예외 전환

public void add(User user) throws DuplicateUserIdException, SQLException {
  try {
    // JDBC를 이용해 user 정보를 DB에 추가하는 코드 또는
    // 그런 기능을 가진 다른 SQLException을 던지는 메소드를 호출하는 코드
  }
  catch(SQLException e) {
    // ErrorCode가 MySQL의 "Duplicate Entry(1062)"이면 예외 전환
    if (e.getERrorCode() == MysqlErrorNumbers.ER_DUP_ENTRY)
      throw DuplicateUserException();
    else
      throw e; // 그 외의 경우는 SQLException 그대로
  }
}
  • 첫번째 방법으로 범용적인 예외를 더 구체적인 예외로 전환하여 예외를 던지는 방법이다.
try {
  ...
} catch (NamingException ne) {
  throw new EJBException(ne);
} catch (SQLException se) {
  throw new EJBException(se);
} catch (RemoteException re) {
  throw new EJBException(re);
}
  • 두번째 방법으로 예외를 처리하기 쉽게 단순히 만드는 포장 방법이 있다.
  • EJBException은 RuntimeException을 상속한 클래스로 예외처리를 좀 더 쉽게 할 수 있도록 포장하는 방식을 사용한 예이다.
    • RuntimeException 을 상속하면 언체크 예외가 되는 것을 기억해두자.

예외처리 전략

런타임 예외의 보편화 (예외 전환)

  • 어차피 복구 못할 예외라면, 그냥 런타임 예외(언체크 예외)로 변경하여 불필요한 throws를 작성하지 않아도 되도록 변경하자.

애플리케이션 예외로 처리하기

  • 복구 가능한 예외 혹은 꼭 한번 생각해봐야 할 예외를 체크드 예외로 처리하여 무엇인가 조치를 취하게 하자

어떤 전략이 좋은가?

  • 결국 상황마다 다르지만, 체크 예외의 경우 예외 블랙홀을 만드는 경우도 있고 생각처럼 개발자들은 귀찮은 예외처리를 꼼꼼하게 하지 않는다. 의미없는 catch 와 throws exception 만 코드에 추가되어 불편함만 가중시키고 있다.
  • JdbcTemplate 과 스프링 프레임워크의 대부분의 구현은 런타임 예외의 보편화 전략을 따르고 있다.
  • 그러나 반드시 예외를 처리하는 것을 고려해야 하는 중요한 비즈니스의 경우에는 애플리케이션 예외 방식을 사용하자.

스프링의 DataAccessException을 통한 예외 전환

  • SQLException 은 보통 작성한 SQL 에 대한 예외이므로 재시도 한다고 해서 복구되기는 어려운 예외이면서도 체크 예외이다. JdbcTemplate 은 DataAccessException 을 통해 SQLException 을 런타임 예외로 전환해 불필요한 catch/throws 를 줄여준다.

JDBC 예외처리의 어려움

  • 대표적으로 DB를 제공하는 벤더문제가 있다. 벤더는 MySQL, Oracle, MSSQL 등 다양하다. 다들 비슷하지만, 각각의 SQL 문법(비표준 SQL)이 있다.
  • 이와 같이 모든 벤더는 비슷한 에러에도 다른 예외를 제공한다.

DataAccessException의 해결책

<bean id="Oracle" class="org.springframework.jdbc.support.SQLErrorCodes">
        <property name="badSqlGrammarCodes">
            <value>900,903,904,917,936,942,17006,6550</value>
        </property>
        <property name="invalidResultSetAccessCodes">
            <value>17003</value>
        </property>
        <property name="duplicateKeyCodes">
            <value>1</value>
        </property>
        <property name="dataIntegrityViolationCodes">
            <value>1400,1722,2291,2292</value>
        </property>
        <property name="dataAccessResourceFailureCodes">
            <value>17002,17447</value>
        </property>
        <property name="cannotAcquireLockCodes">
            <value>54,30006</value>
        </property>
        <property name="cannotSerializeTransactionCodes">
            <value>8177</value>
        </property>
        <property name="deadlockLoserCodes">
            <value>60</value>
        </property>
</bean>
  • DataAccessException 는 벤더별 에러코드를 파악하여, 통합 예외를 제공한다.

통합 예외를 제공하는 이유

  • 위에서 말했듯 런타임 예외로 감싸 catch/throws를 줄여준다.
  • 높은 추상수준의 코드를 이용하여 DB 벤더가 바뀌어도 자바 코드 자체는 바꾸지 않아도 되도록 만들어준다.
    • 기술에 의존적이지 않은 예외처리를 한다는 특징 때문에 가능한 것이다.

예제 코드

@Test
@DisplayName("존재하지 않는 회원을 조회할 때")
public void getUserFailure() {
    // 스프링이 제공하는 EmptyResultDataAccessException 예외가 나타나게 만들자.
    assertThrows(EmptyResultDataAccessException.class, () -> {
        userDao.get("not_existing_user_id");
    });
}

@Test
@DisplayName("QueryForObject를 이용해 2개 이상의 Row 결과가 나왔을 때")
public void getUserFailure2() {
    userDao.add(new User("user1", "김똘일", "1234"));
    userDao.add(new User("user2", "김똘일", "1234"));

    assertThrows(IncorrectResultSizeDataAccessException.class, () -> {
        userDao.getByName("김똘일");
    });
}

userDao 는 스프링 JdbcTemplate 을 이용하여 DB 에 접근하는 오브젝트이다. JdbcTemplate 은 스프링이 제공하는 통합 예외를 던진다.

  • EmptyResultDataAccessException 은 DB 벤더가 아닌 스프링에서 제공하는 예외이다.
  • 사용 기술 혹은 DB에 의존하지 않는 독립적인 추상수준 높은 코드를 만들 수 있다.

사용자 정의 예외로 예외 더욱 구체화하기

public class DuplicateUserIdException extends RuntimeException{
    public DuplicateUserIdException(Throwable cause) {
        super(cause);
    }
}

public void add(User user) throws DuplicateUserIdException {
    try {
        this.jdbcTemplate.update("insert into users(id, name, password, level, login_count, recommend_count) values (?, ?, ?, ?, ?, ?)"
                , user.getId()
                , user.getName()
                , user.getPassword()
                , user.getLevel().intValue()
                , user.getLoginCount()
                , user.getRecommendCount()
        );
    } catch (DuplicateKeyException e) {
        throw new DuplicateUserIdException(e);
    }
}
  • 예외를 한번 더 감싸서 더 구체적으로 만들 수 있다.
  • DataAccessException의 하위 예외인 DuplicateKeyException을 상속받아 DuplicateUserIdException 클래스를 구성했다.
    • 예외의 의미도 훨씬 명확해졌으며, 런타임 예외라서 체크를 강요하지도 않는다.

개발에 적용해볼 점

  • 수동적으로 SpringFramework에 있는 예외 구조만 사용하지 말고, 직접 예외를 랩핑하여 더욱 명시적이며 용도에 맞는 예외를 만들어보도록 하자.

정리

  • 예외를 잡아서 아무런 조치도 취하지 않거나 throws를 남발하는 것은 위험하다.
  • 예외는 복구하거나 예외처리 오브젝트로 의도적으로 전달하거나 적절한 예외로 전환해야 한다.
  • 좀 더 의미 있는 예외로 변경하거나, 불필요한 catch/throws를 피하기 위해 런타임 예외로 포장하는 두가지 방법의 예외 전환이 있다.
  • 복구할 수 없는 예외는 가능한 한 빨리 런타임 예외로 전환하는 것이 바람직하다.
  • 애플리케이션의 로직을 담기 위한 예외는 체크 예외로 만든다.
  • JDBC의 SQLException은 대부분 복구할 수 없는 예외이므로 런타임 예외로 포장해야 한다.
  • SQLException의 에러 코드는 DB에 종속되기 때문에 DB에 독립적인 예외로 전환될 필요가 있다.
  • 스프링은 DataAccessException을 통해 DB에 독립적으로 적용 가능한 추상화된 런타임 예외 계층을 제공한다.
  • DAO를 데이터 액세스 기술에서 독립시키려면 인터페이스 도입과 런타임 예외 전환, 기술에 독립적인 추상화된 예외로 전환이 필요하다.
저작자표시 비영리 (새창열림)

'프레임워크 > 토비의 스프링' 카테고리의 다른 글

토비의 스프링 5장 요약 정리 - 서비스 추상화  (0) 2022.09.06
토비의 스프링 3장 요약 정리 - 템플릿  (0) 2022.06.20
토비의 스프링 2장 요약 정리 - 테스트  (2) 2021.12.26
토비의 스프링 1장 요약 정리 - 오브젝트와 의존관계  (3) 2021.12.26
토비의 스프링 0장 정리  (0) 2021.12.14
    '프레임워크/토비의 스프링' 카테고리의 다른 글
    • 토비의 스프링 5장 요약 정리 - 서비스 추상화
    • 토비의 스프링 3장 요약 정리 - 템플릿
    • 토비의 스프링 2장 요약 정리 - 테스트
    • 토비의 스프링 1장 요약 정리 - 오브젝트와 의존관계
    Jake Seo
    Jake Seo
    ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바