김주엽
1. 📌 핵심 개념 정리
✅ 요약하기
-
오류 코드보다 예외를 사용하라
알고리즘과 오류를 처리하는 알고리즘을 분리하기 위해 예외를 사용하라-
개선 전
public void shutDown() { DeviceHandle handle - getHandle(DEV1); if (handle != DeviceHandle.INVALID) { ... } }
-
개선 후
public void shutDown() { try { tryToShutDown(); } catch(DeviceShutDownError e) { logger.log(e); } }
-
-
Try-Catch-Finally 문부터 작성하라
try-catch-finally
문에서try
블록에 들어가는 코드를 실행하면 어느 시점에서든 실행이 중단된 후catch
블록으로 넘어갈 수 있다.catch
블록은 프로그램의 상태를 일관성 있게 유지해야 한다.- 예외가 발생할 코드를 작성할 때는
try-catch-finally
문을 먼저 작성하는 것이 좋다. - 강제로 예외를 일으키는 테스트 케이스를 작성하고 테스트를 통과하게 하는 코드를 작성하는 방법을 권장한다.
-
개선 전
@Test(expected = StorageException.class) public void retrieveSectionShouldThrowOnInvalidFileName() { sectionStore.retrieveSection("invalid - file"); } public List<RecordedGrip> retrieveSection(String sectionName) { return new ArrayList<RecordedGrip>(); }
이 경우 예외를 던지지 않기에 파일이 없으면 항상 테스트를 실패할 가능성이 있다.
-
개선 후
public List<RecordedGrip> retrieveSection(String sectionName) { try { FileInputStream stream = new FileInputStream(sectionName); stream.close(); } catch (FileNotFoundException e) { throw new StorageException("retrieval error", e); } return new ArrayList<RecordedGrip>(); }
코드 개선 후 파일이 없으면 예외를 항상 던지기에 프로그램의 상태를 유지할 수 있다.
- 미확인 예외를 사용하라
- 확인된 예외는
OCP(Open Closed Principle)
원칙을 위반할 수 있다.- 한 메서드에서 확인된 예외를 던졌을 때
catch
블록은 메서드를 호출하는 블록 세 단계 위에 있다면
그 사이에 있는 모든 메서드의 해당 예외를 다시 정의해야 한다.
- 한 메서드에서 확인된 예외를 던졌을 때
- 미확인 예외 예시
NullPointerException
,RuntimeException
,ArithmeticException
,IndexOutOfBoundsException
등
- 확인된 예외는
- 예외에 의미를 제공하라
- 자바는 모든 예외에 호출 스택을 제공하지만 오류에 대한 파악을 위해서는 스택만으로 부족하다.
- 오류 메시지에 정보를 담아 예외와 함께 던져라.
- 실패한 연산 이름, 유형, 로깅 등을 활용해서 충분한 정보를 제공해라.
-
호출자를 고려해 예외 클래스를 정의하라
오류를 정의할 때 프로그래머에게 가장 중요한 것은 오류를 잡아내는 방법이 되어야 한다.-
개선 전
ACMEPort port = new ACMEPort(12); try{ port.open(); } catch (DeviceResponseException e) { reportPortError(e); } catch (ATM1212UnlockedException e) { reportPortError(e); } catch (GMXError e) { reportPortError(e); } finally { ... }
위 코드는 대부분의 프로그래머가 작성하는 방식으로 중복이 심하다.
-
개선 후
public class LocalPort { private ACMEPort innerPort; public LocalPort(int portNumber) { innerPort = new ACMEPort(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); } } ... } LocalPort port = new LocalPort(12); try { port.open(); } catch (PortDeviceFailure e) { reportError(e); logger.log(e.getMessage(), e); } finally { ... }
예외에 대한 처리를
wrapper
클래스로 감싸고 예외 유형을 하나로 통일시켜 반환했다.
이는 외부 API를 사용할 때 감싸기 기법을 사용하면 매우 효과적이다.
-
-
null을 반환하지 마라
null
을 반환하는 코드는 일을 늘릴 뿐만 아니라 호출자에게 문제를 떠넘긴다.- 누구 하나라도
null
체크를 빼먹는다면 어플리케이션이 망가질 위험이 크다. - 메서드에서
null
을 반환하고 싶은 생각이 든다면 예외를 던지거나 특수 사례 객체를 반환해라.
-
개선 전
List<Employee> employees = getEmployees(); if (employees != null) { for(Employee e : employees) { totalPay += e.getPay(); } }
-
개선 후
List<Employee> employees = getEmployees(); for(Employee e : employees) { totalPay += e.getPay(); } public List<Employee> getEmployees() { if ( employees.isEmpty() ) { return Collections.emptyList(); } }
코드 개선 후 빈 리스트를 반환하여
NullPointerException
이 발생할 가능성을 감소시켰다.
- null을 전달하지 마라
- 인수로
null
을 전달하면 당연히NullPointerException
이 발생한다. - 예외를 만들어 던지는 방법도 있지만
assert
문을 사용하는 방법도 있다.- 예시
public class MetricsCalculator { public double xProjection(Point p1, Point p2) { assert p1 != null : "p1 shoud not be null"; assert p2 != null : "p2 shoud not be null"; return (p2.x - p1.x) * 1.5; } }
- 예시
- 애초에
null
을 인수로 넘기지 못하도록 금지하는 방법이 바람직하다.
- 인수로
2. 🤔 이해가 어려운 부분
🔍 질문하기
- 미확인 예외를 사용하라
- 어려웠던 부분
확인된 예외와 미확인 예외가 정확히 무엇인지 몰라서 이해하는데 어려웠다. - 이해한 점
- 확인된 예외(Checked Exception)
RuntimeException
을 상속하지 않는 예외- 예외를 처리하지 않으면 컴파일 에러가 발생함
- 반드시
try-catch
문으로 처리하거나throws
키워드로 호출한 곳에서 처리하도록 선언해야함. IOException
,SQLException
,FileNotFoundException
,InterruptedException
- 미확인 예외(Unchecked Exception)
RuntimeExecption
을 상속받는 예외- 예외 처리를 하지 않아도 컴파일 에러가 발생하지 않음
- 실행 중에 오류가 발생할 수 있음
- 잘못된 코드 로직으로 인해서 발생하는 경우가 대다수임
null
값에 접근하는 경우- 잘못된 배열 인덱스를 접근하는 경우
- 메서드에 잘못된 인수를 전달한 경우
NullPointerException
,ArrayIndexOutOfBoundsException
,ArithmeticException
,IllegalArgumentException
,NumberFormatException
- 확인된 예외(Checked Exception)
- 어려웠던 부분
3. 📚 참고 사항
📢 논의하기
- 관련 자료 공유
- Try-Catch-Finally 문부터 작성하라
- 주제
예외 처리를 할 때try-catch
문을 사용하여 직접 처리하는 방법과throw
키워드를 사용하여
예외를 상위로 전달하는 방법이 있습니다. 각각 어떤 상황에서 사용하는 것이 적절할까요?
- 주제
1 Comment
try-catch 사용 vs throw 사용
즉, 현재에서 예외 처리 vs 상위에서 예외 처리 -> 각각 어떤 상황에서 적절한가?
- try-catch 사용 (현재에서 처리)예외가 복구 가능할 때 (예: 재시도, 기본값 사용), 로그를 남길 때, 예외가 상위 로직과 무관할 때
- throw 사용 (상위에서 처리)현재에서 해결할 방법이 없을 때, 예외 처리가 비즈니스 로직과 분리되어야 할 때, 라이브러리나 공용 API에서 호출한 쪽에 예외 처리를 맡길 때