Notice
Recent Posts
Recent Comments
Link
관리 메뉴

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

[iOS - swift] Clean Code(클린 코드) - 3. 함수 (1) 본문

Clean Code (클린 코드)

[iOS - swift] Clean Code(클린 코드) - 3. 함수 (1)

jake-kim 2021. 11. 11. 23:57

함수

  • 가능한 하나의 함수는 20줄도 길다고 생각하여, 짧게 만드는것이 가독성에 중요한 요소
    • 하나의 함수가 길어진다면, 그 길어지는 것들을 또다른 함수로 정의하여 그것을 호출하도록 설계
  • 함수 안의 if, guard문의 블록은 여러줄이 아닌 한줄로 구현
  • 하나의 함수는 한가지의 일만 하도록 구현

ex) 페이지를 입력받아서 HTML로 변환하는 함수 정의

- 입력받은 페이지가 테스트 페이지인 경우, 설정페이지(setup)와 해제페이지(teardown)에 페이지를 넣고 해당 테스트 페이지를 HTML로 렌더링하는 기능

 

WRONG

- if문 블록이 한줄을 넘어가므로 잘못된 코드

- 하나의 함수에서 다양한 일 처리 (페이지를 가져오고, 설정페이지와 해제페이지를 추가하는 작업)

func renderPageWithSetupsAndTeardowns(_ page: Page) -> String {
    let isTestPage = page.contains("Test")
    if isTestPage {
        let testPage = page.getWikiPage()
        var newPageContent = PageContent()
        includeSetup(newPageContent, to: testPage)
        newPageContent.append(page.getContent())
        includeTeardown(newPageContent, to: testPage)
        page.setContent(newPageContent)
    }
    return page.getHtml()
}

RIGHT

- if문 블록 안을 한줄로 유지

- 하나의 함수에서 하나의 일만 처리 (설정페이지와 해제페이지를 테스트페이지에 넣는 작업)

func renderPageWithSetupsAndTeardowns(_ page: Page) -> String {
    if isTestPage(page) {
        includeSetupAndTeardownPages(to: page)
    }
    return page.getHtml()
}

하나의 함수는 하나의 일만 해야한다는 원칙

  • 하나의 함수에서 한가지의 일만 한다는 정확한 의미
    • `함수의 이름`아래에서 추상화 수준이 하나라는 의미
    • 위 리펙토링된 코드도 어떻게보면 3가지의 일을 하고 있는 것(테스트 페이지인지 확인, 페이지 삽입, HTML로 렌더링)이지만, 함수 이름 아래에 추상화 수준에서 볼땐 일이 하나인 상태
  • 함수 하나당 추상화 수준은 하나로 유지할 것
    • 함수가 확실히 한 가지 작업만 하려면 함수 내 모든 문장의 추상화 수준이 동일해야 가능
    • 추상화 수준이 섞이게 되면 코드를 읽는 사람이 헷갈리고, 코드가 추가될 때 깨어진 창문처럼 점점 더럽혀지는 코드로 변화
    • 위 코드에서는 추상화 수준이 하나로 유지되지 않은 형태 - getHeml()은 추상화 수준이 높고, append("\n")은 추상화가 낮은 수준
func someFunc(_ page: Page) -> String {
    var testPage = page.getWikiPage() 
    testPage.append("\n") // 추상화 수준이 낮은 편
    return page.getHtml() // 추상화 수준이 높은 편
}

Switch 문

  • switch 문에서 가장 주의해야 하는 점
    • switch문은 애초부터 키워드가 많이 존재하므로 (switch, case) 작게 만들기 힘든 점
    • switch문은 N가지를 처리하므로 한 가지 작업만 하는 switch문을 만들기 어려운 점
  • 위와같은 문제를 해결하기 위해서, 저차원 클래스에 숨기고 다형성을 이용

WRONG

- 함수가 긴 문제, 만약 새 직원 유형이 추가되면 더 길어지는 현상

- 한 가지 작업만 수행하지 않는 점

- 하나의 actor에 대해서 책임지는 함수가 아니므로, 코드를 변경할 이유가 여러가지 될것이므로 SRP를 위반

- 새 직원 유형을 추가할 때마다 코드를 변경해야하기 때문에 OCP위반

- 아래 함수와 동일한 구조의 함수가 여러개 생길 가능선 존재 ... isPayday(employee:date:), deliverPay(employee:pay:)

func calculatePay(_ employee: Employee) -> Money throws InvalidEmplyeeType {
    switch employee {
        case .commissioned:
            return calculateCommissionedPay(employee)
        case .hourly:
            return calculateHourlyPay(employee)
        case .salaried:
            return calculateSalariedPay(employee)
        default:
            return InvalidImployeeType(employee.type)
    }
}

RIGHT

- switch문을 사용하긴 하는데, 외부에서는 interface를 통해 접근하도록 추상화하여, 사용하는쪽에서는 switch의 존재를 모르게하여 외부에 따라 switch에 변경이 생기지 않도록 설계

- 론 제프리스(Ron Jeffries)의 3가지 원칙(중복 줄이기, 표현력 높이기, 초반부터 추상화 고려) 중에 추상화를 고려하면 생기는 장점

protocol Employee {
    var isPayday: Bool { get }
    
    func calculatePay() -> Money
    func deliverPay(pay: Money)
}

protocol EmployeeFactory {
    func makeEmployee(employeeRecord: EmployeeRecord) throws -> Employee
}

class EmplyFactoryImpl: EmployeeFactory {
    func makeEmployee(employeeRecord: EmployeeRecord) throws -> Employee {
        switch employeeRecord {
            case .commissioned:
            return CommissionedEmployee(employeeRecord)
            case .hourly:
            return HourlyEmployee(employeeRecord)
            case .salaried:
            return SalariedEmployee(employeeRecord)
            default:
            throw InvalidImployeeTypeError(employee.type)
        }
    }
}

* 참고: Clean Code (로버트 C. 마틴)

Comments