开发者俱乐部

标题: 为什么Swift中应该避免使用guard语句 [打印本页]

作者: jack    时间: 2016-3-24 18:57
标题: 为什么Swift中应该避免使用guard语句
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. }
复制代码



虽然总行数变多了,但你应该记住,代码行数并不是它的最终目的。
重构后的代码相对于旧版的多了几个优点:
结论
Guard语句很便于用来减少结构体和函数的嵌套,但是问题不是guard本身,而是它的使用。Guard语句会促使你写出能做好几件事、有多层抽象层次的大函数。只要保证所写的函数小而明确,你根本无需guard语句。







欢迎光临 开发者俱乐部 (http://xodn.com/) Powered by Discuz! X3.2