新技术论坛
搜索
查看: 1027|回复: 0
打印 上一主题 下一主题

[iOS/OS X] 为什么Swift中应该避免使用guard语句

[复制链接]
  • TA的每日心情
    开心
    2016-10-18 06:23
  • 签到天数: 72 天

    连续签到: 1 天

    [LV.6]常住居民II

    扫一扫,手机访问本帖
    楼主
    跳转到指定楼层
    发表于 2016-3-24 18:57:27 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
    Guard语句很便于用来减少结构体和函数的嵌套,但是问题不是guard本身,而是它的使用。Guard语句会促使你写出能做好几件事、有多层抽象层次的大函数。只要保证所写的函数小而明确,你根本无需guard语句。


    [url=http://s4.51cto.com/wyfs02/M01/7D/EE/wKiom1bzS3uREGBkAAAT3qqB9Fw843.jpg-wh_651x-s_1041177156.jpg][/url]
    自从guard语句在Swift中出现以来,就引起了大量的讨论。讲真,guard确实简化了代码,且提高了代码的可读性,但它就是灵丹妙药了吗?
    小函数
    关于函数的大小,人们也讨论了很多。很明显,函数体应该是短小的,而且是越短越好。没有人想去读、理解或者重构大函数。但是,函数应该多大才是正确的呢?
    函数的首要准则是--短。次要准则是--更短。---罗伯特`C`马丁
    更具体点说,马丁认为函数的长度应该小于六行,绝对不能大于十行。
    规则虽简,疗效显著,你可以看到代码立刻就变得易懂多了。以前,你需要记住一个有着30行代码,若干缩进层次,若干中间变量的函数。而现在只需要记住十个名字一目了然的函数。
    单一职责
    单一职责这件事也被说了很久。这条规则不仅适用于对象,也同样适用于函数。很显然,每一个函数应该只做一件事情,但是人们一次两次地违反此规则,似乎大部分是因为函数的大小。如果将一个30行的函数重构为十个3行的函数,那么在函数层次自然而然地遵循单一职责规则了。
    单层抽象
    人们对函数的单层抽象讨论得并不多.它是一个有助于写出单一职责函数的工具。
    什么是单层抽象? 简单地说,就是高抽象层次的代码,例如控制进程的代码中不应该混杂着小的细节,例如变量自增或者布尔值检验。举个栗子。
    下面例子出自The Swift Programming Language book。
    1. struct Item {
    2.     var price: Int
    3.     var count: Int
    4. }
    5.   
    6. enum VendingMachineError: ErrorType {
    7.     case InvalidSelection
    8.     case InsufficientFunds(coinsNeeded: Int)
    9.     case OutOfStock
    10. }
    11.   
    12. class VendingMachine {
    13.     var inventory = [
    14.         "Candy Bar": Item(price: 12, count: 7),
    15.         "Chips": Item(price: 10, count: 4),
    16.         "Pretzels": Item(price: 7, count: 11)
    17.     ]
    18.   
    19.     var coinsDeposited = 0
    20.   
    21.     func dispense(snack: String) {
    22.         print("Dispensing \(snack)")
    23.     }
    24.   
    25.     func vend(itemNamed name: String) throws {
    26.         guard var item = inventory[name] else {
    27.             throw VendingMachineError.InvalidSelection
    28.         }
    29.   
    30.         guard item.count > 0 else {
    31.             throw VendingMachineError.OutOfStock
    32.         }
    33.   
    34.         guard item.price <= coinsDeposited else {
    35.             throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
    36.         }
    37.   
    38.         coinsDeposited -= item.price
    39.         --item.count
    40.         inventory[name] = item
    41.         dispense(name)
    42.     }
    43. }
    复制代码



    当然,vend(itemNamed只是使用gaurd语句的一个例子,不过你经常能在生产代码中看到相似的函数。这个函数就出现了三个上面说到的问题:
    • 它相当的长,有十六行,很多部分由空行隔开。
    • 它做了好几件事:通过名称拿到一件商品,验证参数合法性,然后是出售商品的逻辑。
    • 它有好几个抽象层次。最高层的出售过程被隐藏在了更细层次的细节当中了,例如布尔值验证,特殊常量的使用,数学运算等等。
    这个函数重构后是什么样呢?
    1. func vend(itemNamed name: String) throws {
    2.     let item = try validatedItemNamed(name)
    3.     reduceDepositedCoinsBy(item.price)
    4.     removeFromInventory(item, name: name)
    5.     dispense(name)
    6. }
    7.   
    8. private func validatedItemNamed(name: String) throws -> Item {
    9.     let item = try itemNamed(name)
    10.     try validate(item)
    11.     return item
    12. }
    13.   
    14. private func reduceDepositedCoinsBy(price: Int) {
    15.     coinsDeposited -= price
    16. }
    17.   
    18. private func removeFromInventory(var item: Item, name: String) {
    19.     --item.count
    20.     inventory[name] = item
    21. }
    22.   
    23. private func itemNamed(name: String) throws -> Item {
    24.     if let item = inventory[name] {
    25.         return item
    26.     } else {
    27.         throw VendingMachineError.InvalidSelection
    28.     }
    29. }
    30.   
    31. private func validate(item: Item) throws {
    32.     try validateCount(item.count)
    33.     try validatePrice(item.price)
    34. }
    35.   
    36. private func validateCount(count: Int) throws {
    37.     if count == 0 {
    38.         throw VendingMachineError.OutOfStock
    39.     }
    40. }
    41.   
    42. private func validatePrice(price: Int) throws {
    43.     if coinsDeposited < price {
    44.         throw VendingMachineError.InsufficientFunds(coinsNeeded: price - coinsDeposited)
    45.     }
    46. }
    复制代码



    虽然总行数变多了,但你应该记住,代码行数并不是它的最终目的。
    重构后的代码相对于旧版的多了几个优点:
    • 核心的出售函数变小了,而且只包含了出售一个商品的步骤的高层逻辑.如果读者对细节不感兴趣,通过快速地看这个高层函数,她就明白了售卖过程。
    • 这些函数更好地遵守了单一职责原则.其中有一些还可以进一步分解地更小,不过即使是当前的形式,它们都更易读易懂.它们将老的一大串的代码分解成了更小的,一目了然的代码块。
    • 每个函数只负责单一层次逻辑的抽象。读者可以根据需要在不同层次间移动。那出售的过程是什么样的呢?根据名称确定商品是否有效,然后减少顾客的余额,再将商品从存货清单中移除,最后显示此商品已卖出?怎么知道商品是否有效呢?通过检查数量和价格,那怎么知道确切的数量呢?通过和0做比较。如果读者对细节毫无兴趣,他完全可以忽略这部分内容。
    结论
    Guard语句很便于用来减少结构体和函数的嵌套,但是问题不是guard本身,而是它的使用。Guard语句会促使你写出能做好几件事、有多层抽象层次的大函数。只要保证所写的函数小而明确,你根本无需guard语句。


    高级模式
    B Color Image Link Quote Code Smilies

    本版积分规则

    手机版|Archiver|开发者俱乐部 ( ICP/ISP证:辽B-2-4-20110106号 IDC证:辽B-1-2-20070003号 )

    GMT+8, 2024-12-24 10:15 , Processed in 0.122909 second(s), 25 queries .

    X+ Open Developer Network (xodn.com)

    © 2009-2017 沈阳讯网网络科技有限公司

    快速回复 返回顶部 返回列表