Notice
Recent Posts
Recent Comments
Link
관리 메뉴

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

[Refactoring] 7-4. 기능 이동 (함수 옮기기, 중첩함수 제거, 모듈성) - 문장을 호출한 곳으로 옮기기 본문

Refactoring (리펙토링)

[Refactoring] 7-4. 기능 이동 (함수 옮기기, 중첩함수 제거, 모듈성) - 문장을 호출한 곳으로 옮기기

jake-kim 2023. 4. 19. 01:07

리펙토링 핵심

  • 각 방법들을 '왜' 수행해야 하는지 깨닫고 유연하게 적용하기

문장을 호출한 곳으로 옮기기

  • 한 함수는 한 가지의 일만 해야하는데 두 가지 이상의 일을 하는 경우, 함수 안의 문장을 밖으로 빼내는 작업
    • 함수는 프로그래머가 쌓아 올리는 추상화의 기본 빌딩 블록이지만 추상화의 경계를 항상 올바르게 긋기가 어려움
    • 기능 범위가 달라지면 추상화의 경계도 움직이게 되므로 함수 관점에서 응집도가 높고 한 가지 일만 수행해야하는 함수가 둘 이상의 다른 일을 수행하게 될 수 있으므로 이런 부분들을 리펙토링하는것
  • 반대 리펙토링: 문장을 함수로 옮기기

문장을 호출한 곳으로 옮기기

문장을 호출한 곳으로 옮기기 리펙토링

ex) photo, person 데이터가 있고, 이것을 렌더링하는 함수가 있는 상태

  • getRecentPhotos(_:)에서도 emit(photo:)를 호출하는 상황
  • 이때 getRecentPhotos(_:) 메소드에서는 location 정보를 emit(photo:)에서 호출하는 방식과 다르게 commit해야하는 경우 리펙토링?
    • emit(photo:) 부분의 location 커밋 부분은 공통적으로 사용하지 않도록 리펙토링 하는 것
    • 코드는 깃허브 참고
var recentDate: Date?

private func render(person: Person) {
    Storage.commit(message: "이름: \(person.name)")
    render(photo: person.photo)
    emit(photo: person.photo)
}

private func render(photo: Photo?) {
    // render photo...
}

private func emit(photo: Photo?) {
    Storage.commit(message: "제목: \(photo?.title)")
    Storage.commit(message: "날짜: \(photo?.date)")
    Storage.commit(message: "위치: \(photo?.location)")
}

private func getRecentPhotos(_ photos: [Photo]) -> [Photo] {
    let recentPhotos = photos
        .filter { $0.date?.timeIntervalSince1970 ?? 0 < recentDate?.timeIntervalSince1970 ?? 0 }
    recentPhotos
        .forEach { photo in
            Storage.commit(message: "\(photo) start")
            emit(photo: photo)
            Storage.commit(message: "\(photo) end")
        }
    return recentPhotos
}

struct Person {
    var name: String?
    var photo: Photo?
}

struct Photo {
    var title: String?
    var date: Date?
    var location: String?
}

enum Storage {
    static func commit(message: String) {
        // commit message ...
    }
    static func store(value: Any) {
        // store value ...
    }
}
  • emit(photo:)를 호출하는 부분인 render(person:) 사이드 이펙을 막으면서 리펙토링하기 위해서 render(person:) 부분을 수정
// before
private func render(person: Person) {
    Storage.commit(message: "이름: \(person.name)")
    render(photo: person.photo)
    emit(photo: person.photo)
}

// after
private func render(person: Person) {
    Storage.commit(message: "이름: \(person.name)")
    render(photo: person.photo)
    
    // emit대신 temp()와 emit안에 있던 위치 관련 문장 끌어내기
    temp(photo: person.photo)
    Storage.commit(message: "위치: \(person.photo?.location)")
}
  • getRecentPhotos(_:)에서도 emit 호출 부분을 수정
// before
private func getRecentPhotos(_ photos: [Photo]) -> [Photo] {
    let recentPhotos = photos
        .filter { $0.date?.timeIntervalSince1970 ?? 0 < recentDate?.timeIntervalSince1970 ?? 0 }
    recentPhotos
        .forEach { photo in
            Storage.commit(message: "\(photo) start")
            emit(photo: photo)
            Storage.commit(message: "\(photo) end")
        }
    return recentPhotos
}

// after
private func getRecentPhotos(_ photos: [Photo]) -> [Photo] {
    let recentPhotos = photos
        .filter { $0.date?.timeIntervalSince1970 ?? 0 < recentDate?.timeIntervalSince1970 ?? 0 }
    recentPhotos
        .forEach { photo in
            Storage.commit(message: "\(photo) start")
            
            // emit(photo:) 대신 temp와 emit안에 있던 위치 관련 문장 끌어내기
            temp(photo: photo)
            Storage.commit(message: "위치: \(photo.location)")
            
            Storage.commit(message: "\(photo) end")
        }
    return recentPhotos
}
  • emit(photo:)호출하는 부분을 모두 변경하였으므로 emit(photo:)를 지우고, temp(photo:)를 emit(photo:)로 변경한 후, temp(photo:)를 호출하던 부분도 모두 emit(photo:)로 변경하면 완료
// before
private func emit(photo: Photo?) {
    temp(photo: photo)
    Storage.commit(message: "위치: \(photo?.location)")
}
func temp(photo: Photo?) {
    Storage.commit(message: "제목: \(photo?.title)")
    Storage.commit(message: "날짜: \(photo?.date)")
}

// after
func emit(photo: Photo?) {
    Storage.commit(message: "제목: \(photo?.title)")
    Storage.commit(message: "날짜: \(photo?.date)")
}

핵심

  • emit(photo:)안의 내용을 변경하고 싶은 경우, temp(photo:)와 같은 간단한 함수를 만든 후 이것을 적용시키며 동시에 테스트까지하면서 코드를 변경할때마다 빌드가 성공되게끔 할 것
  • 가끔 빌드가 실패되게끔 변경하다보면 개발자의 실수가 들어가기 때문에 항상 빌드가 성공하게끔 temp(photo:)와 같은 함수를 만들어서 진행할것

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

* 참고

- Refactoring (Marting Flowler)

 

Comments