본문 바로가기
책/Clean Code

[Clean Code] 7 장 오류 처리

by 오오오니 2025. 3. 17.

7 장 오류 처리

2024년 2월 13일 오후 8:16
비어 있음
뭔가 잘못될 가능성은 늘 존재한다. 뭔가 잘못되면 바로 잡을 책임은 바로 우리 프로그래머에게 있다.
깨끗한 코드와 오류처리는 연관성이 있다. 흩어진 오류 처리 코드 때문에 코드가 하는 일을 파악하기가 어려워진다면 깨끗한 코드라 부르가 어렵다.

✏️ 오류 코드보다 예외를 사용하라

오류 코드를 반환한다면 호출한 직후 오류를 확인해야 하지만 이 단계는 잊어버리기 쉽다.
오류가 발생하면 예외를 던지는 편이 좋다.
복사
public class DeviceController { ... public void sendShutDown() { DeviceHandle handle = getHandle(DEV1); // 디바이스 상태를 점검한댜. if (handle != DeviceHandle.INVALID) { // 레코드 필드에 디바이스 상태를 저장한다. retrieveDeviceRecord(handle); // 디바이스가 일시정지 상태가 아니라면 종료한다. if (record.getStatus() != DEVICE_SUSPENDED) { / pauseDevice(handle); clearDeviceWorkQueue(handle); closeDevice(handle); } else { logger.log("Device suspended. Unable to shut down"); } } else { logger.log("Invalid handle for: " + DEV1.toString()); } } ... }
→ 오류를 발견하면 예외를 던지게 수정
복사
public class DeviceController { ... public void sendShutDown() { try { tryToShutDown(); } catch (DeviceShutDownError e) { logger.log(e); } } //디바이스 종료 private void tryToShutDown() throws DeviceShutDownError { DeviceHandle handle = getHandle(DEV1); DeviceRecord record = retrieveDeviceRecord(handle); pauseDevice(handle); clearDeviceWorkQueue(handle); closeDevice(handle); } //오류를 처리하는 알고리즘 private DeviceHandle getHandle(DeviceID id) { ... throw new DeviceShutDownError("Invalid handle for: " + id.toString()); ... } ... }

✏️ Try-Catch-Finally 문부터 작성하라

예외가 발생하는 코드를 짤 때는 try-catch-finally 문으로 시작하는 편이 좋다.
강제로 예외를 일으키는 테스트 케이스를 작성한 후 테스트를 통과하게 코드를 작성하기
try 블록의 트랜잭션 범위부터 구현하게 되므로 범위 내에서 트랜잭션 본질을 유지하기 쉬움

✏️ 미확인 예외를 사용하라

확인된 예외가 반드시 필요하지 않다는 사실이 분명해졌다.
확인된 예외는 OCP를 위반한다.
최하위 함수를 변경해 새로운 오류를 던질 때 확인된 오류르 던진다면, 최하위에서 최상위 단계까지 연쇄적인 수정이 일어난다. ⇒ throws 경로에 위치하는 모든 함수가 최하위 함수에서 던지는 예외를 알아야 하므로 캡슐화가 깨진다.

✏️ 예외에 의미를 제공하라

자바는 모든 예외에 호출 스택을 제공하지만 실패한 코드의 의도를 파악하려면 호출 스택만으로 부족하다.
오류 메시지에 정보를 담아 예외와 함께 던지기

✏️ 호출자를 고려해 예외 클래스를 정의하라

오류를 분류하는 방법은 많지만 중요한 관심사는 오류를 잡아내는 방법이다.
외부 라이브러리 호출
예외를 모두 잡아냄
복사
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"); } finally { ... }
wrapper 클래스로 감쌈
예외 유형과 무관하게 동일하게 대응
복사
LocalPort port = new LocalPort(12); try { port.open(); } catch (PortDeviceFailure e) { reportError(e); logger.log(e.getMessage(), e); } finally { ... }
LocalPort 클래스 : ACMPPort 클래스가 던지는 예외를 잡아 변환하는 Wrapper 클래스
복사
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); } } ... }
외부 api에 대한 의존성이 줄고 테스트하기 쉬워진다.
프로그램이 깨끗해진다.

✏️ 정상 흐름을 정의하라

외부 API를 감싸 독자적인 예외를 던지는 것이 대개는 멋진 처리 방식이지만, 때로는 중단이 적합하지 않을 때도 있다.
총계를 계산하는 코드 - 예외가 논리를 따라가기 어렵게 만든
복사
try { MealExpenses expenses = expenseReportDAO.getMeals(employee.getID()); m_total += expenses.getTotal(); } catch(MealExpencesNotFound e) { m_total += getMealPerDiem(); }
DAO를 수정해 청구한 식비가 없다면 일일 기본 식비를 반환하게 함
복사
public class PerDiemMealExpenses implements MealExpenses { public int getTotal() { // 기본값으로 일일 기본 식비를 반환한다. // (예외가 아닌) } }
복사
MealExpenses expenses = expenseReportDAO.getMeals(employee.getID()); m_total += expenses.getTotal();
⇒ 이를 특수 사례 패턴이라고 한다.

✏️ null을 반환하지 마라

문제점
일거리를 늘린다.
호출자에게 문제를 떠넘긴다.
null 반환대신 예외를 던지거나 특수 사례 객체를 반환하기

✏️ null을 전달하지 마라

메서드로 null을 전달하는 코드는 최대한 피한다.
인수로 전달된 Null을 체크해서 새로운 예외 유형을 만들어 던지거나 assert문을 사용할 수 있지만 해결법은 아니다.

✏️ 결론

깨끗한 코드는 읽기도 좋아야 하지만 안전성도 높아야 한다. 오류 처리를 프로그램 논리와 분리해 독자적인 사안으로 고려하면 튼튼하고 깨끗한 코드를 작성할 수 있다.
조혜온
2024. 02. 13.
DAO는 리포지터리 같은 것인데 비지니스 로직이 들어가게 되는건 아닌가?

' > Clean Code' 카테고리의 다른 글

[Clean Code] 11장 시스템  (0) 2025.03.17
[Clean Code] 9장 단위 테스트  (0) 2025.03.17
[Clean Code] 5장 형식 맞추기  (0) 2025.03.17
[Clean Code] 3장 함수  (0) 2024.01.30
[Clean Code] 1장 깨끗한 코드  (2) 2024.01.30