關(guān)于 guard 的另一種觀點(diǎn)

作者:Erica Sadun食寡,原文鏈接,原文日期:2016-01-01
譯者:walkingway廓潜;校對(duì):saitjr抵皱;定稿:shanks

今天,iOS Dev 周刊 貼出一篇 Alexei Kuznetsov 的關(guān)于『從你的代碼中刪除 guard 』的文章辩蛋。Kuznetsov 指出支持他這篇文章的理論依據(jù)主要來自于 Robert C. Martin呻畸,這位世界頂級(jí)軟件開發(fā)大師提出:代碼必須精簡(jiǎn)。即關(guān)于函數(shù)存在兩條規(guī)則悼院,第一條:函數(shù)應(yīng)該保持精簡(jiǎn)伤为;第二條:沒有最精簡(jiǎn),只有更精簡(jiǎn)据途。Alexei Kuznetsov 表示應(yīng)將 Martin 的理論應(yīng)用在今后的 Swift 開發(fā)中绞愚。

Kuznetsov 寫到『使用 guard 語(yǔ)句能有效減少函數(shù)中的嵌套數(shù)量叙甸,但 guard 存在一些問題。使用 guard 語(yǔ)句會(huì)使我們?cè)谝粋€(gè)函數(shù)中做更多的事情爽醋,以及維護(hù)多個(gè)級(jí)別的抽象蚁署。如果我們堅(jiān)持短小、功能單一的函數(shù)蚂四,就會(huì)發(fā)現(xiàn)根本不需要 guard』光戈。

我寫這篇文章的目的是為了反駁 Kuznetsov 提出的觀點(diǎn),接下來我要說說我的看法遂赠。

代碼

下面的代碼片段來自于蘋果官方《Swift Programming Language》書中的示例久妆,他設(shè)計(jì)了一個(gè)虛擬的自動(dòng)販賣機(jī)。 vend 函數(shù)實(shí)現(xiàn)了『顧客成功付款后跷睦,將商品分發(fā)到消費(fèi)者手中』的功能筷弦。如果我沒數(shù)錯(cuò)的話,官方提供的原始函數(shù)一共是 18 行代碼(25 ~ 42 行)抑诸,這個(gè)數(shù)量包括三條 guard 語(yǔ)句烂琴,四條執(zhí)行語(yǔ)句,以及他們之間的換行符蜕乡。

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 重構(gòu)了官方自動(dòng)販賣機(jī)的代碼奸绷,去掉 guard 語(yǔ)句,并盡量縮減了每個(gè)函數(shù)的語(yǔ)句數(shù)量层玲。恕我直言号醉,我不喜歡這種重構(gòu),看完他的代碼來解釋下原因辛块。

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)
    }
}

重構(gòu)的結(jié)果不但冗長(zhǎng)畔派,而且復(fù)雜

Kuznetsov 的主要目標(biāo)是縮減函數(shù)的尺寸。但重構(gòu)的結(jié)果卻是『將之前 18 行代碼驟增到 46 行』润绵,并且將這些邏輯分散在至少八個(gè)函數(shù)中线椰。這種形式的重構(gòu)降低了代碼的可讀性,一個(gè)簡(jiǎn)單的線性故事變成了一個(gè)混亂的集合尘盼,沒有清晰的業(yè)務(wù)邏輯士嚎。

重構(gòu)之后,新的 vend 函數(shù)依賴七個(gè)方法調(diào)用』谶矗現(xiàn)在開始進(jìn)入你的思維殿堂莱衩,想象當(dāng)用戶點(diǎn)擊了販賣按鈕,此刻你將注意力放在這些新觸發(fā)的方法調(diào)用上娇澎,為了理解整個(gè)流程笨蚁,不得不分散你的注意力在這些方法上反復(fù)游走。

Kuznetsov 將一個(gè)統(tǒng)一的函數(shù)分割開來,這里我要引用一篇 George Miller 的論文:神奇數(shù)字 7括细。不僅是因?yàn)?8 明顯比 1 大伪很,更是因?yàn)椤耗芗凶⒁饬Α徊攀?Martin 簡(jiǎn)化函數(shù)的主要目的。針對(duì)這些問題 Kuznetsov 的重構(gòu)顯然是不及格的奋单。

重構(gòu)將『先決條件』視為一個(gè)單獨(dú)的任務(wù)

下面的批評(píng)有點(diǎn)不客氣锉试,Kuznetsov 誤解了 guard 的作用。在他的文章中览濒,guard 的作用是減少嵌套呆盖。我覺得他根本就不懂 guard,正如我之前文章中的觀點(diǎn)贷笛,guard 同樣也是 assert/precondition 大家族中重要的一員:『一般意義上的 guard 語(yǔ)句定義了執(zhí)行的先決條件应又,同樣也提供在不滿足條件時(shí),引導(dǎo)大家撤退的安全路線乏苦≈昕福』

Kuznetsov’s 重新設(shè)計(jì)的斷言被歸為一個(gè)斷言樹。主功能函數(shù) validateItemNamed 首先會(huì)調(diào)用 validate汇荐,接著洞就,validate 分別去調(diào)用其內(nèi)部的兩個(gè)驗(yàn)證方法: validateCountvalidatePrice。我認(rèn)為這種基于樹的布局很難閱讀且不易維護(hù)掀淘,也增加了不必要的復(fù)雜性奖磁。

當(dāng)錯(cuò)誤發(fā)生時(shí),你必須要從錯(cuò)誤發(fā)生節(jié)點(diǎn)回溯到最初調(diào)用 try vend 地方繁疤。比如資金不足會(huì)導(dǎo)致 validatePrice 驗(yàn)證失敗,然后退回到 validate秕狰,再退回到 validatedItemNamed稠腊,最后回到引發(fā)失敗的始作俑者 vend。這只是一個(gè)簡(jiǎn)單的錯(cuò)誤鸣哀,但卻走了很長(zhǎng)一段路架忌。我們可以認(rèn)定:這種將『驗(yàn)證任務(wù)』從『使用任務(wù)』中分離出來的做法是不正確的。

在蘋果的官方版本中我衬,三條 guard 語(yǔ)句通過預(yù)先檢查『輸入』和『狀態(tài)』叹放,來限制對(duì)核心功能的訪問。更重要的是挠羔,guard 說明了繼續(xù)執(zhí)行下面代碼的先決條件井仰。通過?運(yùn)用 guard 語(yǔ)句,Apple 在斷言(assertions)和動(dòng)作(actions)之間建立了一種直接聯(lián)系破加,即:如果測(cè)試通過俱恶,就執(zhí)行這些動(dòng)作。

斷言(assertions)和動(dòng)作(actions)之間的協(xié)同定位至關(guān)重要。在將來做代碼審查時(shí)合是,可以通過這些行為(actions)的上下文來檢查這些測(cè)試了罪,有必要的話,進(jìn)行更新聪全、修改泊藕、刪除這些操作也很方便。他們與被守護(hù)代碼之間难礼,近似地建立起一條重要連接娃圆。

在代碼中我推薦使用 guard 來做基本的安全檢查,并堅(jiān)持認(rèn)為蘋果官方(自動(dòng)售貨機(jī))才是 guard 使用的正確姿勢(shì)鹤竭。最后總結(jié)一下:?你或許有自己使用 guard 的方式踊餐,但是這樣做并不會(huì)對(duì)你的代碼帶來好處。

本文由 SwiftGG 翻譯組翻譯臀稚,已經(jīng)獲得作者翻譯授權(quán)吝岭,最新文章請(qǐng)?jiān)L問 http://swift.gg

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吧寺,一起剝皮案震驚了整個(gè)濱河市窜管,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稚机,老刑警劉巖幕帆,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赖条,居然都是意外死亡失乾,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門纬乍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碱茁,“玉大人,你說我怎么就攤上這事仿贬∨ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵茧泪,是天一觀的道長(zhǎng)蜓氨。 經(jīng)常有香客問我,道長(zhǎng)队伟,這世上最難降的妖魔是什么穴吹? 我笑而不...
    開封第一講書人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮嗜侮,結(jié)果婚禮上刀荒,老公的妹妹穿的比我還像新娘代嗤。我一直安慰自己,他們只是感情好缠借,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開白布干毅。 她就那樣靜靜地躺著,像睡著了一般泼返。 火紅的嫁衣襯著肌膚如雪硝逢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評(píng)論 1 290
  • 那天绅喉,我揣著相機(jī)與錄音渠鸽,去河邊找鬼。 笑死柴罐,一個(gè)胖子當(dāng)著我的面吹牛徽缚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播革屠,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼凿试,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了似芝?” 一聲冷哼從身側(cè)響起那婉,我...
    開封第一講書人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎党瓮,沒想到半個(gè)月后详炬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寞奸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年呛谜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枪萄。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡隐岛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呻引,到底是詐尸還是另有隱情,我是刑警寧澤吐咳,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布逻悠,位于F島的核電站,受9級(jí)特大地震影響韭脊,放射性物質(zhì)發(fā)生泄漏童谒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一沪羔、第九天 我趴在偏房一處隱蔽的房頂上張望饥伊。 院中可真熱鬧象浑,春花似錦、人聲如沸琅豆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)茫因。三九已至蚪拦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冻押,已是汗流浹背驰贷。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洛巢,地道東北人括袒。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像稿茉,于是被迫代替她去往敵國(guó)和親锹锰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,799評(píng)論 25 707
  • Hello Word 在屏幕上打印“Hello, world”狈邑,可以用一行代碼實(shí)現(xiàn): 你不需要為了輸入輸出或者字符...
    restkuan閱讀 3,171評(píng)論 0 6
  • 領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(DDD)旨在軟件設(shè)計(jì)過程中提煉領(lǐng)域模型城须,以領(lǐng)域模型為核心改善業(yè)務(wù)專家和軟件開發(fā)者的溝通方式,對(duì)企業(yè)級(jí)...
    MagicBowen閱讀 5,471評(píng)論 0 29
  • 帶著夏日的激情米苹,我走進(jìn)九月的巴黎糕伐。 留學(xué)?堂皇而美麗的借口蘸嘶!愛情的誘惑吧良瞧,這是他的城市。我想象的翅膀鼓滿了風(fēng)训唱,璀璨...
    繁花私語(yǔ)閱讀 329評(píng)論 0 3
  • 天際有幾片輪廓不太明顯的云褥蚯,幾乎是與作為背景的明橘色與淺粉色糅雜在一起的天空融合在一塊。安德爾和苜蓿已經(jīng)走到山林的...
    蘋香閱讀 201評(píng)論 0 0