관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] Clean Code(클린 코드) - 6. 오류 처리 본문

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. 마틴)

Comments