Swift 6 : Typed Throws ( 에러도 타입을 줘서 더 명확히 처리하자.)
Swift 6의 Typed Throws: 오류 처리의 새로운 패러다임
Swift 6에서 도입된 "typed throws"는 Swift의 오류 처리 시스템에 중요한 발전을 가져왔습니다. 이 기능을 통해 함수가 발생시킬 수 있는 구체적인 오류 유형을 명시적으로 선언할 수 있게 되었습니다.
Swift의 기존 오류 처리 방식
Swift 5까지는 함수가 오류를 던질 수 있다는 것만 표시할 수 있었고, 어떤 종류의 오류를 던질 수 있는지는 명시할 수 없었습니다:
enum DatabaseError: Error {
case connectionFailed
case queryFailed
}
enum NetworkError: Error {
case timeout
case serverDown
}
// 이 함수는 어떤 오류든 던질 수 있음을 나타내지만, 구체적으로 어떤 오류인지는 명시하지 않습니다
func fetchUserData() throws -> UserData {
// 구현...
}
이 접근 방식의 한계는 호출하는 쪽에서 이 함수가 어떤 종류의 오류를 발생시킬 수 있는지 알 수 없다는 것입니다. 문서나 코드를 살펴봐야만 알 수 있었습니다.
Swift 6의 Typed Throws 소개
Swift 6에서는 함수가 던질 수 있는 오류 유형을 명시적으로 선언할 수 있게 되었습니다.
// 이 함수는 DatabaseError만 던질 수 있음을 명시합니다
func fetchUserData() throws(DatabaseError) -> UserData {
// 구현...
}
// 이 함수는 DatabaseError와 NetworkError를 던질 수 있음을 명시합니다
func syncData() throws(DatabaseError, NetworkError) -> Bool {
// 구현...
}
Typed Throws의 장점
- 명확한 계약: 함수 시그니처를 통해 함수가 던질 수 있는 오류 유형이 명확하게 드러납니다.
- 컴파일 타임 검사: 컴파일러가 함수가 선언되지 않은 오류 유형을 던지는 것을 방지할 수 있습니다.
- 더 정확한 오류 처리: 호출하는 쪽에서 처리해야 할 오류 유형을 정확히 알 수 있습니다.
- 코드 문서화: 코드 자체가 어떤 오류가 발생할 수 있는지 문서화합니다.
실제 사용 예시
실제 코드에서 typed throws를 어떻게 사용하고 처리하는지 살펴보겠습니다.
enum DatabaseError: Error {
case connectionFailed
case queryFailed
case insufficientPermissions
}
enum ValidationError: Error {
case invalidInput
case missingField(String)
}
// 함수 선언 - 특정 오류 유형만 던짐
func saveUser(_ user: User) throws(DatabaseError, ValidationError) -> Bool {
guard user.isValid() else {
throw ValidationError.invalidInput
}
guard user.name.count > 0 else {
throw ValidationError.missingField("name")
}
if !database.isConnected {
throw DatabaseError.connectionFailed
}
// 데이터 저장 로직...
return true
}
// 호출 측에서의 오류 처리
func createNewUser() {
do {
let success = try saveUser(newUser)
print("User saved successfully")
} catch let error as DatabaseError {
switch error {
case .connectionFailed:
print("Could not connect to database")
case .queryFailed:
print("Database query failed")
case .insufficientPermissions:
print("Not enough permissions to save user")
}
} catch let error as ValidationError {
switch error {
case .invalidInput:
print("User data is invalid")
case .missingField(let fieldName):
print("Required field missing: \(fieldName)")
}
} catch {
// 이 부분은 실행되지 않을 것입니다. 왜냐하면 saveUser 함수는
// DatabaseError와 ValidationError만 던지기 때문입니다.
print("Unknown error: \(error)")
}
}
기존 throws와의 호환성
Swift 6는 기존 코드와의 호환성을 유지하기 위해 일반적인 throws도 계속 지원합니다. 이는 모든 가능한 오류를 던질 수 있음을 의미합니다:
// 여전히 유효한 코드입니다 - 어떤 Error든 던질 수 있음
func legacyFunction() throws -> String {
// 구현...
}
함수 합성과 오류 전파
Typed throws는 함수 합성과 오류 전파에서도 이점을 제공합니다.
func processUserData() throws(DatabaseError, ValidationError, NetworkError) -> ProcessedData {
// saveUser는 DatabaseError와 ValidationError만 던질 수 있습니다
let saved = try saveUser(user)
// fetchAdditionalData는 NetworkError만 던질 수 있습니다
let additionalData = try fetchAdditionalData(user.id)
// 이 함수는 세 가지 오류 유형을 모두 전파할 수 있습니다
return ProcessedData(saved: saved, additional: additionalData)
}
서브타이핑과 다형성
Typed throws는 서브타이핑 규칙을 따릅니다. 부모 클래스에서 특정 오류를 던지는 메서드를 선언하면, 자식 클래스는 같은 오류나 그 하위 집합만 던질 수 있습니다.
class DataService {
func fetchData() throws(NetworkError, DatabaseError) -> Data {
// 구현...
}
}
class CachedDataService: DataService {
// 유효함: 부모보다 적은 오류 유형을 던짐
override func fetchData() throws(NetworkError) -> Data {
// 구현...
}
}
class InvalidService: DataService {
// 컴파일 오류: 부모가 선언하지 않은 추가 오류 유형을 던질 수 없음
override func fetchData() throws(NetworkError, DatabaseError, ValidationError) -> Data {
// 구현...
}
}
제네릭과의 상호 작용
Typed throws는 제네릭 프로그래밍과도 잘 통합됩니다.
// E는 Error 프로토콜을 준수하는 어떤 타입이든 될 수 있습니다
func processGeneric<T, E: Error>(value: T) throws(E) -> T {
// 구현...
}
// 여러 제네릭 오류 타입을 사용할 수도 있습니다
func complexOperation<E1: Error, E2: Error>(data: Data) throws(E1, E2) -> Result {
// 구현...
}
정리
Swift 6의 typed throws는 코드의 안전성과 명확성을 크게 향상시키는 기능입니다. 이 기능을 통해 프로그래머는 함수가 발생시킬 수 있는 오류 유형을 명시적으로 문서화할 수 있으며, 컴파일러는 이를 기반으로 더 엄격한 검사를 수행할 수 있습니다. 이는 더 견고하고 유지보수가 용이한 코드를 작성하는 데 도움이 되며, 런타임에 발견될 오류를 컴파일 타임에 잡아낼 수 있게 해줍니다.