본문 바로가기
Five Lines Of Code

[iOS] 파이브 라인즈 오브 코드: 1~3장 요약 및 정리 (리팩터링: 왜 필요하고 어떻게 할까?)

by Jimmy_iOS 2024. 7. 23.

리팩터링: 왜 필요하고 어떻게 할까?

 

안녕하세요, 여러분!

최근에 제가 개발하면서 느꼈던 불편함과 이를 해결하기 위해 리팩터링을 어떻게 적용했는지 공유해보려고 합니다.

 

요즘 iOS 개발을 하면서 코드가 점점 복잡해지는 걸 느꼈어요.

 

특히 유지보수가 어려워지고, 새로운 기능을 추가할 때마다 버그가 생기는 문제를 겪었죠.

그래서 이번 기회에 리팩터링에 대해 진지하게 고민해보게 되었습니다.

 

이 책을 통해 여러분은 다음과 같은 내용을 배울 수 있습니다:

 

코드 가독성 향상: 가독성이 높은 코드를 작성하는 방법

효율적인 함수와 변수 사용법: 함수와 변수를 효율적으로 사용하는 방법

조건문과 반복문의 최적화: 조건문과 반복문을 최적화하여 성능을 높이는 방법

코드 분할과 리팩터링: 코드를 작은 단위로 분할하고 리팩터링하는 방법


1장: 함수와 변수의 역할

함수는 무엇을 해야 할까?

함수는 하나의 작업만 수행해야 해요.

 

여러 작업을 하는 함수는 유지보수가 어렵고, 테스트하기도 힘들죠.

그래서 함수는 작은 단위로 분리해야 합니다.

 

예를 들어, 숫자의 합을 구하는 함수는 다음과 같이 단순하게 작성할 수 있어요:

 

func sum(_ a: Int, _ b: Int) -> Int {
    return a + b
}

 

하지만 현실에서는 이렇게 단순하지 않죠. 더 복잡한 예시를 들어볼게요.

 

func processOrder(order: Order) -> Double {
    var total = 0.0

    if order.items.count > 0 {
        for item in order.items {
            total += item.price
        }

        if order.discount > 0 {
            total -= total * order.discount
        }

        if order.tax > 0 {
            total += total * order.tax
        }
    }

    return total
}

 

이 코드는 여러 작업을 한 함수에서 처리하고 있어요.

이렇게 작성된 함수는 테스트하기도 어렵고,

나중에 수정하기도 힘듭니다.

 

여기서 '코드스멜'이 나는 거죠.

 

코드스멜이란, 코드가 잘못되었음을 암시하는 일련의 징후를 말합니다.

이를 해결하기 위해 함수들을 작은 단위로 분리해 보겠습니다.

리팩터링 후:

func processOrder(order: Order) -> Double {
    var total = calculateTotal(items: order.items)
    total = applyDiscount(total: total, discount: order.discount)
    total = applyTax(total: total, tax: order.tax)
    return total
}

func calculateTotal(items: [Item]) -> Double {
    return items.reduce(0) { $0 + $1.price }
}

func applyDiscount(total: Double, discount: Double) -> Double {
    return discount > 0 ? total - (total * discount) : total
}

func applyTax(total: Double, tax: Double) -> Double {
    return tax > 0 ? total + (total * tax) : total
}

 

이제 각 함수가 하나의 작업만 수행하도록 분리되었어요.

이렇게 하면 가독성이 높아지고, 수정하기도 훨씬 쉬워졌어요.


2장: 조건문과 반복문의 최적화

조건문 단순화

 

조건문이 복잡해질수록 가독성이 떨어지고 버그가 발생할 가능성이 높아집니다. 조건문을 단순화하는 방법을 살펴볼게요. 예를 들어, 복잡한 if-else 문을 다음과 같이 단순화할 수 있습니다:

 

if isUserLoggedIn {
    showWelcomeMessage()
} else {
    showLoginPrompt()
}

 

하지만 현실에서는 조건이 더 복잡해지죠. 예를 들어, 여러 단계의 조건문이 있을 때:

 

func processPayment(method: String, amount: Double) {
    if method == "CreditCard" {
        if amount > 1000 {
            print("Large Credit Card Payment")
        } else {
            print("Credit Card Payment")
        }
    } else if method == "PayPal" {
        if amount > 500 {
            print("Large PayPal Payment")
        } else {
            print("PayPal Payment")
        }
    } else {
        print("Unknown Payment Method")
    }
}

 

이렇게 작성된 코드는 읽기도 어렵고, 조건이 추가될 때마다 계속해서 수정해야 합니다.

이를 해결하기 위해 리팩터링을 통해 코드를 단순화해보겠습니다.

 

리팩터링 후:

func processPayment(method: String, amount: Double) {
    switch method {
    case "CreditCard":
        print(amount > 1000 ? "Large Credit Card Payment" : "Credit Card Payment")
    case "PayPal":
        print(amount > 500 ? "Large PayPal Payment" : "PayPal Payment")
    default:
        print("Unknown Payment Method")
    }
}

 

이제 조건문이 더 간결해졌고, 가독성도 높아졌어요.

 

반복문의 최적화

반복문은 코드를 간결하게 만들어주지만, 잘못 사용하면 성능 저하를 일으킬 수 있어요.

효율적인 반복문 사용을 위해 컬렉션 메서드를 활용해보세요. 예를 들어:

 

let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }

 

하지만 현실에서는 더 복잡한 반복문을 사용할 때가 많죠. 예를 들어:

 

func calculateTotal(items: [Item]) -> Double {
    var total = 0.0
    for item in items {
        total += item.price
    }
    return total
}

 

이 코드를 더 간결하게 만들 수 있습니다:

 

func calculateTotal(items: [Item]) -> Double {
    return items.reduce(0) { $0 + $1.price }
}

 

이제 반복문도 더 간결해지고, 성능도 향상되었어요.


3장: 코드를 더 작게, 더 간결하게

코드 분할의 중요성

 

코드를 작은 단위로 분할하면 가독성이 높아지고 유지보수가 쉬워집니다.

큰 함수나 클래스를 작은 단위로 분리하세요.

 

예를 들어, 다음과 같은 큰 함수는:

리팩터링 전:

func handleUserInput(input: String) {
    if input.isEmpty {
        print("Input is empty")
        return
    }

    var data = [String]()

    // 데이터를 가져오는 로직
    if input == "user1" {
        data = ["Data1", "Data2"]
    } else if input == "user2" {
        data = ["Data3", "Data4"]
    } else {
        print("No data found")
        return
    }

    if data.isEmpty {
        print("No data found")
        return
    }

    // 데이터를 저장하는 로직
    for item in data {
        print("Saving \(item)")
    }

    print("Data saved successfully")
}

 

이 코드는 여러 작업을 하나의 함수에서 처리하고 있어요.

입력 검증, 데이터 가져오기, 데이터 저장이라는

세 가지 작업을 하나의 함수에서 수행하고 있죠.

 

이렇게 작성된 코드는 유지보수가 어렵고, 테스트하기도 힘듭니다.

이를 해결하기 위해 리팩터링을 통해 코드를 분리해 보겠습니다.

리팩터링 후:

func handleUserInput(input: String) {
    guard isInputValid(input: input) else {
        print("Input is empty")
        return
    }

    let data = fetchData(for: input)
    guard !data.isEmpty else {
        print("No data found")
        return
    }

    saveData(data)
    print("Data saved successfully")
}

func isInputValid(input: String) -> Bool {
    return !input.isEmpty
}

func fetchData(for input: String) -> [String] {
    // 데이터를 가져오는 로직
    return input == "user1" ? ["Data1", "Data2"] : input == "user2" ? ["Data3", "Data4"] : []
}

func saveData(_ data: [String]) {
    // 데이터를 저장하는 로직
    data.forEach { print("Saving \($0)") }
}

 

이제 각 함수가 하나의 작업만 수행하도록 분리되었어요.

이렇게 하면 가독성이 높아지고, 유지보수도 훨씬 쉬워졌습니다.

 


마무리

"파이브 라인즈 오브 코드"의 1장에서 3장까지의 내용을 통해

함수와 변수의 역할, 조건문과 반복문의 최적화, 그리고 코드 분할의 중요성에 대해 배웠습니다.

이러한 원칙을 실제 프로젝트에 적용해보세요.

코드의 가독성과 유지보수성이 크게 향상될 거예요.

 

책에서 인용하자면, "리팩터링은 샤워와 같다. 자주 해야만 그 효과를 볼 수 있다."


여러분도 한번 리팩터링을 시도해보세요.

작은 변화가 큰 차이를 만들 수 있답니다.