guard에 대한 또 다른 관점

5677 단어
저자: Erica Sadun, 원문 링크, 원문 날짜: 2016-01-01 번역자:walkingway;교정:saitjr;원고:shanks
오늘 iOS Dev 주간지는'당신의 코드에서 삭제guard'에 관한 Alexei Kuznetsov의 기사를 게재했다.Kuznetsov는 그의 이 글을 지지하는 이론적 근거는 주로 Robert C. Martin에서 비롯되었다고 지적했다. 이 세계 최고의 소프트웨어 개발 대가는 코드는 반드시 간소화해야 한다고 주장했다.즉, 함수에 관해서는 두 가지 규칙이 존재한다. 첫 번째는 함수는 간소화해야 한다.제2조: 가장 간소한 것은 없고 더 간소한 것만 있다.Alexei Kuznetsov는 Martin의 이론을 향후 Swift 개발에 적용해야 한다고 밝혔다.
"Kuznetsov는""사용guard문장은 함수에 끼워 넣은 수량을 효과적으로 줄일 수 있지만 guard문제가 있습니다. 사용guard문장은 함수에서 더 많은 일을 할 수 있고 여러 단계의 추상화를 유지할 수 있습니다. 짧고 기능이 단일한 함수를 고수한다면 전혀 필요하지 않습니다guard""라고 썼습니다."
내가 이 글을 쓴 목적은 Kuznetsov이 제기한 관점을 반박하기 위해서이다. 다음에 나는 나의 견해를 말하겠다.

코드


다음 코드 에피소드는 애플의 공식'스위프트 프로그램밍 랭거'(Swift Programming Language) 책의 예시에서 나온 것으로, 그는 가상 자판기를 설계했다.vend 함수는'고객이 성공적으로 지불한 후에 상품을 소비자에게 나누어 준다'는 기능을 실현했다.만약 내가 잘못 세지 않았다면 정부에서 제공한 원시 함수는 모두 18줄 코드(25~42줄)로 이 수량은 세 줄guard문구, 네 줄의 집행문구, 그리고 그들 사이의 줄 바꾸기를 포함한다.
struct Item {
    var price: Int
    var count: Int
}

enum VendingMachineError: ErrorType {
    case InvalidSelection
    case InsufficientFunds(coinsNeeded: Int)
    case OutOfStock
}

class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]

    var coinsDeposited = 0
    
    func dispense(snack: String) {
        print("Dispensing \(snack)")
    }

    func vend(itemNamed name: String) throws {
        guard var item = inventory[name] else {
            throw VendingMachineError.InvalidSelection
        }

        guard item.count > 0 else {
            throw VendingMachineError.OutOfStock
        }

        guard item.price <= coinsDeposited else {
            throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }

        coinsDeposited -= item.price
        --item.count
        inventory[name] = item
        dispense(name)
    }
}

Kuznetsov은 공식 자판기의 코드를 재구성해 guard 문장을 제거하고 함수별 문장 수를 최소화했다.솔직히 말하지만, 나는 이런 재구성을 좋아하지 않는다. 그의 코드를 보고 원인을 설명해라.
func vend(itemNamed name: String) throws {
    let item = try validatedItemNamed(name)
    reduceDepositedCoinsBy(item.price)
    removeFromInventory(item, name: name)
    dispense(name)
}

private func validatedItemNamed(name: String) throws -> Item {
    let item = try itemNamed(name)
    try validate(item)
    return item
}

private func reduceDepositedCoinsBy(price: Int) {
    coinsDeposited -= price
}

private func removeFromInventory(var item: Item, name: String) {
    --item.count
    inventory[name] = item
}

private func itemNamed(name: String) throws -> Item {
    if let item = inventory[name] {
        return item
    } else {
        throw VendingMachineError.InvalidSelection
    }
}

private func validate(item: Item) throws {
    try validateCount(item.count)
    try validatePrice(item.price)
}

private func validateCount(count: Int) throws {
    if count == 0 {
        throw VendingMachineError.OutOfStock
    }
}

private func validatePrice(price: Int) throws {
    if coinsDeposited < price {
        throw VendingMachineError.InsufficientFunds(coinsNeeded: price - coinsDeposited)
    }
}

재구성 의 결과 는 지루할 뿐 만 아니라 복잡하다


Kuznetsov의 주요 목표는 함수의 크기를 줄이는 것이다.그러나 재구성 결과는'전 18줄 코드를 46줄로 급증시켰다'는 논리를 최소 8개 함수에 분산시켰다.이런 형식의 재구성은 코드의 가독성을 떨어뜨리고 간단한 선형 이야기는 혼란스러운 집합으로 바뀌었고 명확한 업무 논리가 없었다.
재구성 후, 새로운 vend 함수는 7개의 방법에 의존하여 호출된다.지금부터 당신의 사고의 전당에 들어가 사용자가 판매 버튼을 눌렀을 때 새로운 촉발 방법에 주의를 기울이고 전체 과정을 이해하기 위해 주의력을 분산시켜 이 방법들을 반복해서 헤엄쳐야 한다고 상상해 보세요.
Kuznetsov는 하나의 통일된 함수를 분리합니다. 여기에서 George Miller의 논문인 신기한 숫자 7을 인용하려고 합니다.8이 1보다 현저하게 크기 때문일 뿐만 아니라'주의력을 집중할 수 있다'는 것이Martin의 간소화 함수의 주요 목적이기 때문이다.이러한 문제에 대한 Kuznetsov의 재구성은 분명히 불합격이다.

재구성은'선결조건'을 하나의 단독 임무로 간주한다


아래의 비판은 좀 천만에요. 쿠즈넷소브가 guard의 역할을 오해했어요.그의 글에서guard의 역할은 끼워 넣는 것을 줄이는 것이다.나는 그가 전혀 이해하지 못한다고 생각한다guard. 내 글에서 말한 바와 같이 guard 역시 assert/precondition 대가족의 중요한 일원이다.'일반적인 의미의guard 문구는 집행의 선결 조건을 정의했고 조건이 충족되지 않을 때 철수하는 안전 노선을 제공한다.'
Kuznetsov's가 재설계한 단언은 단언 트리로 분류됩니다.주요 기능 함수validateItemNamed는 먼저 validate를 호출하고, 이어서 validate는 그 내부의 두 가지 검증 방법을 각각 호출한다. validateCountvalidatePrice.나는 이러한 나무에 기초한 구조는 읽기가 어렵고 유지하기 어렵으며 불필요한 복잡성을 증가시켰다고 생각한다.
오류가 발생했을 때, 오류 발생 노드에서 최초 호출 try vend 곳으로 거슬러 올라가야 합니다.예를 들어 자금 부족은 validatePrice 검증 실패를 초래하고 validate로 되돌아와 validatedItemNamed, 마지막으로 실패를 초래한 시인자vend로 돌아간다.이것은 단지 간단한 잘못일 뿐이지만, 아주 긴 길을 걸었다.우리는 이러한'검증 임무'를'사용 임무'에서 분리하는 방법은 정확하지 않다고 인정할 수 있다.
애플의 공식 버전에서 세 줄guard의 문장은'입력'과'상태'를 미리 검사함으로써 핵심 기능에 대한 접근을 제한한다.더 중요한 것은 guard 아래 코드를 계속 집행하는 선결 조건을 설명했다.엔 운용guard 문장을 통해 애플은 단언(assertions)과 동작(actions) 사이에 직접적인 관계를 맺었다. 즉, 테스트가 통과되면 이 동작을 수행하는 것이다.
단언(assertions)과 동작(actions) 간의 협동 포지셔닝이 매우 중요하다.앞으로 코드 심사를 할 때 이러한 행위(actions)의 상하문을 통해 이러한 테스트를 검사할 수 있으며, 필요하면 업데이트, 수정, 삭제를 하는 것도 편리하다.그들은 수호된 코드와 비슷하게 중요한 연결을 맺었다.
코드에서 나는 guard를 사용하여 기본적인 안전 검사를 하는 것을 추천하고 애플 정부(자동판매기)야말로guard가 사용하는 정확한 자세라고 주장했다.마지막으로 요약하자면: 엔, 당신은 자신이 사용하는 방식이 있을지도 모르지만, 이렇게 하면 당신의 코드에 좋은 점이 없을 것이다.
이 문서는 SwiftGG 번역팀에서 번역하였으며, 작성자 번역 승인을 받았습니다. 최신 기사는http://swift.gg.

좋은 웹페이지 즐겨찾기