기존의 오류코드는 아래와 같았습니다.
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로 넘어간다.
TDD(Test Driven Development) 방식으로 메소드를 구현
1. 단위 테스트를 만든다
@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);
} catch (Exception e) {
throw new StorageException("retrieval error", e);
}
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>();
}
checked 예외
컴파일 단계에서 확인되며 반드시 처리해야 하는 예외
(IOException, SQLException etc...)
unchecked 예외
실행 단계에서 확인되며 확시적인 처리를 강제하지 않는 예외
(NullPointerException, IllegalArgumentException, IndexOuntOfBoundException, SystemException)
checked 예외는 비용이 들기에, 그에 상응하는 이익이 있는지 따져야 함.
(OCP를 위반함, 메소드에서 확인된 예외를 던졌는데 catch 블록이 세 단계 위에 있다면, 그 사이 메소드들 모두에 예외를 정의해야하기 때문)
// 1
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 {
...
}
// 2
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);
}
}
...
}
1번은 외부 라이브러리를 호출하고 모든 예외를 호출자가 잡아낸다.
그에 반해 2번은 호출 라이브러리 API를 감싸 한가지 예외 유형을 반환하는 방식으로 단순화
null을 반환하는 습관은 좋지 않다
// 1
List<Employee> employees = getEmployees();
if(employees != null) {
for(Employee e : employees) {
totalPay += e.getPay();
}
}
// 2
List<Employee> employees = getEmployees();
for(Employee e : employees) {
totalPay += e.getPay();
}
public List<Employee> getEmployees() {
if (..직원이 없다면..)
return Collections.emptyList();
}
1번처럼 null을 반환하지 말고 2번처럼 차라리 빈 list를 반환하게 하거나 assert 문을 사용하는 것도 방법이다.