관리 메뉴

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

[Refactoring] 8-2. 데이터 조작화, 데이터 구조가 불변성이면 좋은 이유, class가 아닌 struct (필드 이름 바꾸기) 본문

iOS 응용 (swift)

[Refactoring] 8-2. 데이터 조작화, 데이터 구조가 불변성이면 좋은 이유, class가 아닌 struct (필드 이름 바꾸기)

jake-kim 2023. 5. 13. 02:03

필드 이름 바꾸기

  • 필드 이름을 변경하려고 할 때 이 필드가 여러곳에서 사용되고 있는 경우 변경 방법은 캡슐화를 통해 리펙토링
  • 필드 이름 바꾸기에서 생각하는 포인트 
    • 변경하려는 필드가 여러곳에서 사용되고 있는 경우, 어떻게 바꿀것인가?
    • 데이터 구조가 불변성으로 표현되면 좋은 이유?

필드 이름 바꾸기

필드 이름 바꾸기 예시

  • 아래에서 name 필드를 title로 변경하고 싶은 경우?
    • 이 값은 여러곳에서 사용되고 있기 때문에 쉽게 바꾸기 힘든 상황
    • name을 단순히 title로 변경하고난 후 빌드에러나는 곳을 찾아서 일괄 변경할 수 있지만 일괄 수정하다가 실수를 유발할 수 있음
    • (swift에서 일반적으로 데이터 모델은 struct를 사용하지만, 불변성의 중요성을 깨닫기 위해 class로 선언)
class MyData {
    var name: String
    var country: String
    
    init(name: String, country: String) {
        self.name = name
        self.country = country
    }
}

func someFunc1(data: MyData) {
    data.name = "name is " + data.name
    data.country = "country is " + data.country
    print(data.name + data.country)
}

func someFunc2(data: MyData) {
    data.name = "name:" + data.name
    data.country = "country:" + data.country
    print(data.name + data.country)
}
  • 캡슐화를 진행하여 해당 값을 사용하고자하는 곳에서는 이 캡슐화된 값을 사용하도록 수정
    • getter, setter가 생겨서 일이 더 많아진거 같지만, 큰 일을 작은 단계들로 나누어서 독립적으로 수행하게끔 변경됨
    • 작은 단계의 의미 - 각 단계에서 실수할 일이 적어진다는 의미
struct DataNew {
    private var _name: String
    private var _country: String
    
    var name: String {
        get { return _name }
        set { _name = newValue }
    }
    var country: String {
        get { return _country }
        set { _country = newValue }
    }
    
    init(myData: MyData) {
        self._name = myData.name
        self._country = myData.country
    }
}
  • 캡슐화를 사용했기 때문에 해당 필드의 이름들은 마음대로 수정이 가능
    • 접근하는 computed 프로퍼티의 이름은 name이 아닌 title로 변경
    • 초기화 문도 title로 초기화할수 있도록 하나 더 추가
    • 사용하는 쪽에서도 해당 데이터를 사용하도록 하나하나씩 수정해나가기
struct DataNew {
    private var _title: String
    private var _country: String
    
    var name: String {
        get { return _title }
        set { _title = newValue }
    }
    var country: String {
        get { return _country }
        set { _country = newValue }
    }
    
    init(myData: MyData) {
        self._title = myData.name
        self._country = myData.country
    }
    
    init(title: String, country: String) {
        self._title = title
        self._country = country
    }
}

데이터 구조가 불변성이면  좋은 이유

  • 위에서 알아본 MyData는 class 타입이고 내부 프로퍼티가 var로 선언되어 불변성이 아닌 형태
class MyData {
    var name: String
    var country: String
    
    init(name: String, country: String) {
        self.name = name
        self.country = country
    }
}

func someFunc1(data: MyData) {
    data.name = "name is " + data.name
    data.country = "country is " + data.country
    print(data.name + data.country)
}

func someFunc2(data: MyData) {
    data.name = "name:" + data.name
    data.country = "country:" + data.country
    print(data.name + data.country)
}
  • 데이터 타입이 class MyData가 아니라 불변성인 struct MyData이면?
    • 함수에 파라미터로 넘어온 data를 가지고 data.name으로 접근할 때, data는 value type이므로 name을 변경하려고해도 data복사를 해야하므로 compile error가 발생
    • name을 변경하려고 해도 불변성이라 변경하지 못함
    • name을 접근하는 코드가 자동으로 사라지므로 name을 title로 변경할때도 변경되는 부분이 적기 때문에 리펙토링이 더욱 용이해짐

  • class타입이 아닌 struct타입으로 불변성을 주면 함수 내부에서는 데이터를 복사(var data = data)해서 접근하는 형태가 됨
func someFunc1_refactor(data: RefactorMyData) {
    var data = data
    data.name = "name is " + data.name
    data.country = "country is " + data.country
    print(data.name + data.country)
}

func someFunc2_refactor(data: RefactorMyData) {
    var data = data
    data.name = "name:" + data.name
    data.country = "country:" + data.country
    print(data.name + data.country)
}
  • 이때 name, country도 모두 불변성으로 let 선언하면 아래처럼 name, country를 변경하려는 코드도 compile error 발생

  • 사용하는쪽에서는 자동으로 생성자를 새로 만들어서 접근하게되고, 이렇게되면 리펙토링을 할 때 생성자에만 관심을 가지고 이 부분만 변경해주면 되므로 리펙토링할때 고려해야할 이펙트가 줄어들어 더욱 쉽게 변경할 수 있는 코드로 탄생
struct RefactorMyData {
    let name: String
    let country: String
    
    init(name: String, country: String) {
        self.name = name
        self.country = country
    }
}

func someFunc1_refactor(data: RefactorMyData) {
    let data = RefactorMyData(
        name: "name is " + data.name,
        country: "country is " + data.country
    )
    print(data.name + data.country)
}

func someFunc2_refactor(data: RefactorMyData) {
    let data = RefactorMyData(
        name: "name:" + data.name,
        country: "country:" + data.country
    )
    print(data.name + data.country)
}

* 전체 코드: https://github.com/JK0369/ExRefactoring8_2

 

* 참고

- Refactoring (Marting Flowler)

Comments