왜
배열 요소에 접근하는 코드를 작성하다 보면 컴파일 타임에는 배열 요소에 접근하는 코드에 문제가 있는지 육안으로는 확인이 되지 않기 때문에 조건에 따른 분기 코드를 통해 접근하는 예외 처리가 되어 있지 않다면 런타임에 'index out of range'와 함께 앱이 종료되는 상황을 마주할 수 있다.
예를 들면, 다음과 같은 코드가 되겠다.
struct Item {
let name: String
}
let items: [Item] = "가나다라마바사".map { itemName in
Item(name: "\(itemName)")
}
let firstName = items[items.count].name
위와 같은 코드는 바로 크래쉬를 발생시키고 앱이 종료될 수도 있다.
(테스트 코드를 통해 로직에 대한 실패 케이스 확인 후 코드의 논리 오류를 수정하는 하는 과정이 있지 않다면 QA에서도 이슈로 확인될 수 있음)
크래쉬로 인한 앱의 종료는 어떤 관점으로 보더라도 큰 문제이기 때문에 코드 작성 시 다음과 같은 코드들을 통해 크래쉬를 피할 수 있다.
//#1
let firstName = items[items.count - 1].name
//#2
if index < items.count {
let firstName = items[index].name
}
개인적으로 배열의 요소에 접근하는 코드는 다음과 같은 관점에서 항상 조심할 필요가 있다고 생각한다.
1. 분기 코드의 논리적 오류가 존재
2. 테스트 코드로 리팩토링 하는 코드에서도 논리적 오류가 존재
>> 이 케이스는 크래쉬까지는 가지 않고, 중간에 계속 수정이 이뤄져서 결국 그 사이에 해결이 되겠지만 추가적인 시간 소요는 피할 수 없을 것 같기 때문.
현재 회사에서 레거시 코드에서 이런 상황을 종종 마주하기도 했었고, 그때마다 분기코드를 매번 넣는 것도 번거롭고, 프로젝트 전반적으로 사용할 목적으로 extension을 어떻게 만들면 좋을까 고민하다가 다음과 같은 코드들을 생각해 보게 됐다.
어떻게
Array에 있는 Property들과 subscript를 이용해서 문제가 없다면 해당 index로 접근한 배열 요소를 리턴, 문제가 있다면 nil을 리턴해서 크래쉬에 대한 안전성을 확보
extension Array {
subscript(safeAccess index: Int) -> Element? {
///#1
// if index >= self.startIndex && index < self.count {
// return self[index]
// }
// return nil
///#2
// guard index >= self.startIndex && index < self.count else {
// return nil
// }
// return self[index]
///#3
// if (self.startIndex ..< self.count).contains(index) {
// return self[index]
// }
// return nil
///#4
// guard (self.startIndex ..< self.count).contains(index) else {
// return nil
// }
// return self[index]
///#5
// if self.indices.contains(index) {
// return self[index]
// }
// return nil
///#6
// guard self.indices.contains(index) else {
// return nil
// }
// return self[index]
///#7
// return (self.startIndex ..< self.count).contains(index) ? self[index] : nil
///#8
self.indices.contains(index) ? self[index] : nil
}
}
let nums: [Int] = [1,3,5]
print(nums[safeAccess: 0]) //Optional(1)
print(nums[safeAccess: 3]) //nil
방법은 위와 같이 다양하고, 입맛에 맞는 코드를 사용하면 될 것 같다.
빈 배열이라면 다음과 같은 결과를 확인할 수 있다.
let nums: [Int] = []
print(nums[safeAccess: 0]) //nil
print(nums[safeAccess: 3]) //nil
추가로, 위에는 Array에 extension코드를 추가해서 해봤지만 Array가 Conform 한 Collection Protocol에 extension코드를 추가해도 같은 동작을 할 것 같다.
무엇을
배열 요소에 안전하게 접근
생각해볼 점
개인적인 생각으로는 Array에 한정 짓는 것도 좋지만, Collection Protocol을 Conform 한 Array와 String에서 동시에 사용되게끔 Collection에 extension 코드를 추가하고, String은 String.Index를 사용하기 때문에 subscript를 만들거나 String에 Extension코드로 Int index로 접근하는 코드를 만들어두고, 위의 subscript코드 하나로 사용하거나 하는 방식을 고려하면 어떨까 싶다. (이건 또 생각이 들면 해보기로..)
'Swift' 카테고리의 다른 글
| Protocol(1) (0) | 2023.08.15 |
|---|---|
| Generics (0) | 2023.08.13 |