
- 오류 처리 코드는 중요하지만 오류 처리 코드로 인해 프로그램 논리를 이해하기 어려워진다면 깨끗한 코드라 부르기 어렵다. 그러므로 우아하게 오류 코드를 처리하는 방법 또한 배워야 한다. 다음은 깔끔하게 오류 코드를 처리할 수 있는 고려 사항들이다.
오류 코드보다 예외를 사용하라 (130p) → 예외를 do try catch 구문으로 이해하면 될 듯
- 오류를 처리하는 메소드를 이용해 오류 코드를 처리하는 방법
- sendShutDown() 메소드 안에 1) 디바이스를 종료하는 알고리즘과 2) 오류를 처리하는 알고리즘이 뒤섞여 코드가 복잡하다.
final class DeviceController {
func sendShutDown() {
let handle = getHandle(dev1: DEV1)
if handle != DeviceHandle.invalid {
retrieveDeviceRecord(handle: handle)
if record.status != DEVICE_SUSPENDED {
pauseDevice(handle: handle)
clearDeviceWorkQueue(handle: handle)
closeDevice(handle: handle)
} else {
logger.log(message: "Device suspended. Unable to shut down")
}
} else {
logger.log(message: "Invalid handle for: \(DEV1)")
}
}
}
- do try catch 구문을 통해 오류를 처리하는 방법 (오류 처리는 do try catch 구문을 사용하기) ⭐️⭐️
- do try catch 구문을 사용하면 디바이스를 종료하는 알고리즘과 오류를 처리하는 알고리즘을 분류하여 훨씬 품질이 좋은 코드가 완성된다.
final class DeviceController {
func sendShutDown() {
do {
try tryToShutDown()
} catch let error as DeviceShutDownError {
logger.log(message: error.localizedDescription)
} catch {
print("Unexpected error: \(error).")
}
}
private func tryToShutDown() throws {
guard let handle = getHandle(devID: DEV1) else {
throw DeviceShutDownError.invalidHandleError("Invalid handle for: \(DEV1)")
}
let record = retrieveDeviceRecord(handle: handle)
pauseDevice(handle: handle)
clearDeviceWorkQueue(handle: handle)
closeDevice(handle: handle)
}
private func getHandle(devID: DeviceID) throws -> DeviceHandle? {
throw DeviceShutDownError.invalidHandleError("Invalid handle for: \(devID)")
}
}
Try-Catch-Finally 문부터 작성하라 (132p)
- 먼저 강제로 do try catch를 일으키는 테스트 케이스를 작성한 후 테스트를 통과하게 코드를 작성하는 방법을 권장한다.
import XCTest
final class SectionStoreTests: XCTestCase {
var sectionStore: SectionStore!
override func setUpWithError() throws {
try super.setUpWithError()
sectionStore = SectionStore()
}
override func tearDownWithError() throws {
sectionStore = nil
try super.tearDownWithError()
}
func testRetrieveSectionShouldThrowOnInvalidFileName() {
XCTAssertThrowsError(try sectionStore.retrieveSection(sectionName: "invalid - file")) { error in
XCTAssertTrue(error is StorageException)
}
}
}
class SectionStore {
func retrieveSection(sectionName: String) throws -> [RecordedGrip] {
do {
let stream = try FileHandle(forReadingFrom: URL(fileURLWithPath: sectionName))
stream.closeFile()
} catch {
throw StorageException.retrievalError
}
return [RecordedGrip]()
}
}
enum StorageException: Error {
case retrievalError
}
struct RecordedGrip {}
미확인 예외를 사용하라 (133p)
- Java에서는 확인된 예외와 미확인된 예외를 구분한다.
- 확인된 예외: 컴파일러가 확인할 수 있는 예외로 일반적으로 애플리케이션의 비즈니스 로직에서 발생하며, 예상 가능하고 복구 가능한 상황에서 발생하는 예외이다. 이러한 예외는 try-catch 블록으로 처리하거나 throws 키워드를 사용하여 호출자에게 전달해야 한다.
- 미확인된 예외: 컴파일러가 확인하지 않는 예외로, 주로 프로그래밍 에러, 예를 들어 null 참조나 배열 범위를 넘어서는 경우 등에 발생한다. 이러한 예외는 RuntimeException을 상속받는다. 이러한 예외는 보통 try-catch 로 처리하지 않고, 프로그램의 결함을 수정하여 예외가 발생하지 않도록 하는 것이 일반적이다.
- Swift는 이러한 구분 없이 모든 예외를 미확인 예외로 취급함 ⭐️⭐️
- Swift 내에서 예외를 처리하려면 do-try-catch 블록을 사용하면 된다.
예외에 의미를 제공하라 (135p)
- throws를 통해 오류를 던질 때에는 전후 상황을 충분히 덧붙인다.
- catch 블록에서 정확한 실패 유형을 언급한다.
호출자를 고려해 예외 클래스를 정의하라 (135p)
- LocalPort 클래스처럼 ACMEPort를 감싸는 방법은 최선의 방법.
class ACMEPort {
let portNumber: Int
init(portNumber: Int) {
self.portNumber = portNumber
}
func open() throws {
}
}
enum PortError: Error {
case deviceResponse
case unlocked
case gmx
}
enum PortDeviceFailure: Error {
case failure(underlyingError: Error)
}
class LocalPort {
private let innerPort: ACMEPort
init(portNumber: Int) {
innerPort = ACMEPort(portNumber: portNumber)
}
func open() throws {
do {
try innerPort.open()
} catch PortError.deviceResponse {
throw PortDeviceFailure.failure(underlyingError: error)
} catch PortError.unlocked {
throw PortDeviceFailure.failure(underlyingError: error)
} catch PortError.gmx {
throw PortDeviceFailure.failure(underlyingError: error)
}
}
}
정상 흐름을 정의하라 (135p)
- catch 상황을 만들어 처리할 필요가 없다면 특수 사례 패턴을 사용해서 처리하면 코드가 훨씬 깔끔해진다.
- 특수 사례 패턴이란 클래스가 특수한 사례를 처리하도록 하는 방식을 의미한다.
do {
let expenses = try expenseReportDAO.getMeals(employeeID: employee.getID())
m_total += expenses.getTotal()
} catch {
m_total += getMealPerDiem()
}
struct MealExpenses {
func getTotal() -> Int {
}
}
let expenses = MealExpenses()
m_total += expenses.getTotal()
결론 (142p)
- 오류 처리를 프로그램 논리와 분리해 독자적으로 구분하면 튼튼하고 깨끗한 코드를 작성할 수 있다. ⭐️⭐️