关于 guard 的另一种观点

作者:Erica Sadun,原文链接,原文日期:2016-01-01
译者:walkingway;校对:saitjr;定稿:shanks

今天,iOS Dev 周刊 贴出一篇 Alexei Kuznetsov 的关于『从你的代码中删除 guard 』的文章。Kuznetsov 指出支持他这篇文章的理论依据主要来自于 Robert C. Martin,这位世界顶级软件开发大师提出:代码必须精简。即关于函数存在两条规则,第一条:函数应该保持精简;第二条:没有最精简,只有更精简。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 行』,并且将这些逻辑分散在至少八个函数中。这种形式的重构降低了代码的可读性,一个简单的线性故事变成了一个混乱的集合,没有清晰的业务逻辑。

重构之后,新的 vend 函数依赖七个方法调用。现在开始进入你的思维殿堂,想象当用户点击了贩卖按钮,此刻你将注意力放在这些新触发的方法调用上,为了理解整个流程,不得不分散你的注意力在这些方法上反复游走。

Kuznetsov 将一个统一的函数分割开来,这里我要引用一篇 George Miller 的论文:神奇数字 7。不仅是因为 8 明显比 1 大,更是因为『能集中注意力』才是 Martin 简化函数的主要目的。针对这些问题 Kuznetsov 的重构显然是不及格的。

重构将『先决条件』视为一个单独的任务

下面的批评有点不客气,Kuznetsov 误解了 guard 的作用。在他的文章中,guard 的作用是减少嵌套。我觉得他根本就不懂 guard,正如我之前文章中的观点,guard 同样也是 assert/precondition 大家族中重要的一员:『一般意义上的 guard 语句定义了执行的先决条件,同样也提供在不满足条件时,引导大家撤退的安全路线。』

Kuznetsov’s 重新设计的断言被归为一个断言树。主功能函数 validateItemNamed 首先会调用 validate,接着,validate 分别去调用其内部的两个验证方法: validateCountvalidatePrice。我认为这种基于树的布局很难阅读且不易维护,也增加了不必要的复杂性。

当错误发生时,你必须要从错误发生节点回溯到最初调用 try vend 地方。比如资金不足会导致 validatePrice 验证失败,然后退回到 validate,再退回到 validatedItemNamed,最后回到引发失败的始作俑者 vend。这只是一个简单的错误,但却走了很长一段路。我们可以认定:这种将『验证任务』从『使用任务』中分离出来的做法是不正确的。

在苹果的官方版本中,三条 guard 语句通过预先检查『输入』和『状态』,来限制对核心功能的访问。更重要的是,guard 说明了继续执行下面代码的先决条件。通过�运用 guard 语句,Apple 在断言(assertions)和动作(actions)之间建立了一种直接联系,即:如果测试通过,就执行这些动作。

断言(assertions)和动作(actions)之间的协同定位至关重要。在将来做代码审查时,可以通过这些行为(actions)的上下文来检查这些测试,有必要的话,进行更新、修改、删除这些操作也很方便。他们与被守护代码之间,近似地建立起一条重要连接。

在代码中我推荐使用 guard 来做基本的安全检查,并坚持认为苹果官方(自动售货机)才是 guard 使用的正确姿势。最后总结一下:�你或许有自己使用 guard 的方式,但是这样做并不会对你的代码带来好处。

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,123评论 25 707
  • Hello Word 在屏幕上打印“Hello, world”,可以用一行代码实现: 你不需要为了输入输出或者字符...
    restkuan阅读 3,137评论 0 5
  • 领域驱动设计(DDD)旨在软件设计过程中提炼领域模型,以领域模型为核心改善业务专家和软件开发者的沟通方式,对企业级...
    MagicBowen阅读 5,370评论 0 29
  • 带着夏日的激情,我走进九月的巴黎。 留学?堂皇而美丽的借口!爱情的诱惑吧,这是他的城市。我想象的翅膀鼓满了风,璀璨...
    繁花私语阅读 321评论 0 3
  • 天际有几片轮廓不太明显的云,几乎是与作为背景的明橘色与浅粉色糅杂在一起的天空融合在一块。安德尔和苜蓿已经走到山林的...
    苹香阅读 195评论 0 0