[Clean Code] 7. 오류처리
이 장에서는 오류처리에 대해 알아본다. 또한 깨끗하고 튼튼한 코드에 한걸음 더 다가가는 단계로 나아간다. 프로그래머에게 오류처리는 매우 중요하지만, 오류 처리코드로 인해 프로그램 논리를 이해하기 어려워지면 안된다.
오류 코드보다 예외를 사용하라
- 이전에는 예외를 지원하지 않는 프로그래밍 언어가 많았으나 지금은 아니다.
- 오류를 if문으로 처리하려 하지 말고, throw로 예외를 던지자
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. Unalbe to shut Down");
}
} else {
logger.log("Invailed handle for: " + DEV1.toString());
}
}
}
- if문으로 처리하는 예시
public class DeviceController {
public void sendShutDown() {
try{
tryToShutDown();
} catch (DeviceShotDownError 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());
}
}
- throw로 예외를 던지는 예시
두 가지의 로직을 분리함으로서 코드도 꺠끗해지고 품질도 나아졌다.
Try-Catch-finally 문부터 작성하라
- 예외가 발생할 코드를 짤때는 try-catch-finally 문으로 시작하자
- catch 블록으로 넘어가더라도 프로그램은 일관성 있게 유지되어야 한다
- 나머지 논리를 추가하기 쉽고 호출자가 기대하는 상태를 정의하기 쉬워진다.
Unchecked 에외를 사용하라
- C#, C++, Python, Ruby는 Checked예외를 지원하지 않아도 안정적인 소프트웨어를 구현하기에 무리가 없다.
- Checked 예외는 최상위 함수가 아래 함수를 호출하고 타고 들어간 최하단 함수가 변경해 새로운 오류를 던진다면, 그 중간에 지나친 함수들도 수정해야 하므로 OCP를 위반한다
예외에 의미를 제공하라
- 에러 오류메시지에 정보를 담아 예외와 함께 던지자
- 로깅 기능이 가능하다면 catch 블록에서 오류를 기록하도록 충분한 정보를 넘겨라
호출자를 고려해 예외 클래스를 정의하라
- 오류의 유형은 다양하나, 가장 중요한건 오류를 잡아내는 방법이다.
- 외부에서 호출하는 라이브러리를 한번 감싸도록 하자. (의존성 감소)
- 외부 클래스의 지정된 예외를 감싼 클래스에 넣어서 처리할 수도 있다.
정상 흐름을 정의하라
- 꼭 예외를 던지고 처리하는 것만 항상 정답은 아니다.
- 가끔은 특수사례 패턴(SPECIAL CASE PATTERN) 로 특수 사례를 처리하는 방식이 나을수 있다.
null을 반환하지 마라
- null을 반환시 매 로직마다 null check 를 하게된다
- 호출자에게 문제를 던져버리는 꼴이다
- 사용하려는 외부 API가 null을 반환한다면 감싸기식 메서드를 구현해 예외를 던지거나, 특수 사례 객체를 반환하는 방식을 고려한다
- null 대신 빈배열을 반환하자 Collections.emptyList()
public void registerItem(Item item) { if(item != null) { ItemRegistry registry = peristentStore.getItemRegistry(); if(registry != null) { Item existing = registry.getItem(item.getID()); if(existing.getBillingPeriod().hasRetailOwner()) { existing.register(item); } } } }
- null 확인 코드 예시
public List<Employee> getEmployees() {
...
if(employee.isNotExist()) {
return Collections.emptyList();
}
}
List<Employee> employees = getEmployees();
- 빈 배열 반환 예시
null을 전달하지 마라
- 메서드에서 또는 메서드로 null을 전달 하는 방식은 매우 나쁘다.
- null이 넘어온다면 위처럼 null 확인코드를 작성하거나 다른 확인 방법이 추가되어야 하므로 지저분 해진다
- 인수로 null이 넘어오면 코드에 문제가 있을 수 있다
마치며..
가장 크게 와닿았던 것은 null을 전달하지 않는것, 그리고 Unchecked 예외를 사용하라 였다. 예시로 개발 도중에 잘못된 param, 잘못된 데이터가 던져지면 확인한 예외라 생각하고 Checked 예외를 던졌다. 크게 동작에 문제는 안되지만 @Transactional 을 이용해 rollback을 고려시 Unchecked 예외만 rollback 된다 하더라. (설정해서 checked 예외도 rollback 할 수 있긴 하다.) 이런 점에서 Checked 예외를 권하는건지? 는 모르겠지만 이런 내용들이 프레임워크에 녹아들어 있는걸까 싶다.
Leave a comment