Clean Code (클린 코드)
[iOS - swift] Clean Code(클린 코드) - 6. 오류 처리
jake-kim
2021. 11. 17. 23:44
오류 코드보다 예외를 지향
- 예외처리는 논리가 들어간 로직부분과 예외를 처리하는 부분을 나누어서 코드가 섞이지 않게되어 복잡해지지 않게되는 장점이 존재
- 오류 코드는 테스트가 힘들지만, 예외를 사용하면 throw에 관한 리턴값을 확인할 수 있어서 TDD에도 용이
WRONG
class DeviceController {
func sendShutDown() -> Void {
if handle != DevcieHandle.INVALID {
if record.getStatus() != DEVICE_SUSPENDED {
pauseDevice(handle)
clearDeviceWorkQueue(handle)
closeDevice(handle)
} else {
Log.error("Device suspended. Unable to shut down")
}
} else {
Log.error("Invalid handle")
}
}
}
RIGHT
class DeviceController {
func sendShutDown() -> Void {
do {
try shutDown()
} catch {
Log.error(error)
}
}
private func shutDown() throws {
let handle = getHandle(DEV1)
let record = retrieveDeviceRecord(handle)
do {
try pauseDevice(handle)
try clearDeviceWorkQueue(handle)
try closeDevice(handle)
} catch {
...
throw ...
}
}
private getHandle(id: DeviceId) -> DeviceHandle {
...
throw DeviceShutDownError.error1
...
}
...
}
예외에 의미를 제공할 것
- 예외를 던질 때는 전후 상황을 충분히 덧붙여서 예외를 이해하기 쉽도록 설계
- 자바는 모든 예외에 호출 스택을 제공하지만, 실패한 코드의 의도를 파악하려면 호출 스택만으로는 부족
- 실패한 연산 이름, 실패 유형 등을 같이 제공하도록 설계 필요
여러가지 유형의 예외가 있는 경우, Wrapper 클래스 사용
- 여러가지 유형의 예외처리가 필요한 경우, Wrapper클래스를 만들어서 간결화
ex) 여러가지 유형의 예외처리가 나열된 경우
let port = ACMEPort(12)
do {
try port.open()
} catch DataException.DeviceResponse {
reportPortError(error)
Log.error("Device response exeception", error)
} catch DataException.ATM1212UnlockedException {
reportPortError(error)
Log.error("Unlock exception", error)
} catch DataException.GMXError {
reportPortError(error)
Log.error("Device response exception")
}
> LocalPort Wrapper 클래스를 만들어서 처리
- 장점1: 해당 모듈을 사용할 때 해당 모듈의 오류들을 다양하게 알지 않아도 되므로 의존성이 줄어드는 장점
- 장점2: 의존성이 줄어들었으므로, 사용하는 입장에서 다른 모듈로 변경에도 용이
class LocalPort {
private innerPort: ACMEPort
init(innerPort: ACMEPort) {
self.innerPort = innerPort
}
func open() {
do {
try port.open()
} catch DataException.DeviceResponse {
throw portDeviceFailure(error)
} catch DataException.ATM1212UnlockedException {
throw portDeviceFailure(error)
} catch DataException.GMXError {
throw portDeviceFailure(error)
}
}
}
let port = LocalPort(12)
do {
try port.open()
} catch DataError.PortDeviceFailure {
reportError(error)
Log.error(error)
}
nil을 반환하는 코드는 지양할 것
- 모듈에서는 가급적 nil을 반환하지 않고 non-optional값을 반환할 것
- 모듈에서 nil을 반환하게 된다면, 호출자에게 문제를 떠넘기는 형태 (아래 예시)
func registerItem(item: Item) {
if item != nil {
let registry = peristentStore.getItemReistry()
if registry != nil {
let existing = registry.getItem(item.getID())
if existing.getBillingPeriod().hasRetailOwner() {
existing.register(item)
}
}
}
}
* 참고: Clean Code (로버트 C. 마틴)