为什么Swift中应该避免使用guard语句
创始人
2025-01-28 05:31:11
0

自从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. enum VendingMachineError: ErrorType { 
  6.     case InvalidSelection 
  7.     case InsufficientFunds(coinsNeeded: Int) 
  8.     case OutOfStock 
  9.   
  10. class VendingMachine { 
  11.     var inventory = [ 
  12.         "Candy Bar": Item(price: 12, count: 7), 
  13.         "Chips": Item(price: 10, count: 4), 
  14.         "Pretzels": Item(price: 7, count: 11) 
  15.     ] 
  16.   
  17.     var coinsDeposited = 0 
  18.   
  19.     func dispense(snack: String) { 
  20.         print("Dispensing \(snack)") 
  21.     } 
  22.   
  23.     func vend(itemNamed name: String) throws { 
  24.         guard var item = inventory[name] else { 
  25.             throw VendingMachineError.InvalidSelection 
  26.         } 
  27.   
  28.         guard item.count > 0 else { 
  29.             throw VendingMachineError.OutOfStock 
  30.         } 
  31.   
  32.         guard item.price <= coinsDeposited else { 
  33.             throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited) 
  34.         } 
  35.   
  36.         coinsDeposited -= item.price 
  37.         --item.count 
  38.         inventory[name] = item 
  39.         dispense(name) 
  40.     } 

当然,vend(itemNamed:)只是使用gaurd语句的一个例子,不过你经常能在生产代码中看到相似的函数。这个函数就出现了三个上面说到的问题:

  1. 它相当的长,有十六行,很多部分由空行隔开。
  2. 它做了好几件事:通过名称拿到一件商品,验证参数合法性,然后是出售商品的逻辑。
  3. 它有好几个抽象层次。最高层的出售过程被隐藏在了更细层次的细节当中了,例如布尔值验证,特殊常量的使用,数学运算等等。

这个函数重构后是什么样呢?

  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. private func validatedItemNamed(name: String) throws -> Item { 
  8.     let item = try itemNamed(name) 
  9.     try validate(item) 
  10.     return item 
  11.   
  12. private func reduceDepositedCoinsBy(price: Int) { 
  13.     coinsDeposited -= price 
  14.   
  15. private func removeFromInventory(var item: Item, name: String) { 
  16.     --item.count 
  17.     inventory[name] = item 
  18.   
  19. private func itemNamed(name: String) throws -> Item { 
  20.     if let item = inventory[name] { 
  21.         return item 
  22.     } else { 
  23.         throw VendingMachineError.InvalidSelection 
  24.     } 
  25.   
  26. private func validate(item: Item) throws { 
  27.     try validateCount(item.count) 
  28.     try validatePrice(item.price) 
  29.   
  30. private func validateCount(count: Int) throws { 
  31.     if count == 0 { 
  32.         throw VendingMachineError.OutOfStock 
  33.     } 
  34.   
  35. private func validatePrice(price: Int) throws { 
  36.     if coinsDeposited < price { 
  37.         throw VendingMachineError.InsufficientFunds(coinsNeeded: price - coinsDeposited) 
  38.     } 

虽然总行数变多了,但你应该记住,代码行数并不是它的最终目的。

重构后的代码相对于旧版的多了几个优点:

  1. 核心的出售函数变小了,而且只包含了出售一个商品的步骤的高层逻辑.如果读者对细节不感兴趣,通过快速地看这个高层函数,她就明白了售卖过程。
  2. 这些函数更好地遵守了单一职责原则.其中有一些还可以进一步分解地更小,不过即使是当前的形式,它们都更易读易懂.它们将老的一大串的代码分解成了更小的,一目了然的代码块。
  3. 每个函数只负责单一层次逻辑的抽象。读者可以根据需要在不同层次间移动。那出售的过程是什么样的呢?根据名称确定商品是否有效,然后减少顾客的余额,再将商品从存货清单中移除,最后显示此商品已卖出?怎么知道商品是否有效呢?通过检查数量和价格,那怎么知道确切的数量呢?通过和0做比较。如果读者对细节毫无兴趣,他完全可以忽略这部分内容。

结论

Guard语句很便于用来减少结构体和函数的嵌套,但是问题不是guard本身,而是它的使用。Guard语句会促使你写出能做好几件事、有多层抽象层次的大函数。只要保证所写的函数小而明确,你根本无需guard语句。

相关阅读

  • View Event Testing in Swift
  • Humble Object Pattern in Swift
  • Safer UIViewController Creation When Using Storyboards
  • Dependency Injection in View Controllers
  • Dependency Injection in Swift

相关内容

热门资讯

PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
30分钟搞定iOS自定义相机 最近公司的项目中用到了相机,由于不用系统的相机,UI给的相机切图,必须自定义才可以。就花时间简单研究...
Intel将Moblin社区控... 本周二,非营利机构Linux基金会宣布,他们将担负起Moblin社区的管理工作,而这之前,Mobli...