选项与投掷功能

时间:2015-06-26 16:03:46

标签: swift

考虑我编写的以下查找函数,即使用选项和可选绑定,如果在字典中找不到键,则报告消息

func lookUp<T:Equatable>(key:T , dictionary:[T:T]) -> T? {
    for i in dictionary.keys {
       if i == key{
           return dictionary[i]
       }
    }
    return nil
}

let dict = ["JO":"Jordan",
   "UAE":"United Arab Emirates",
   "USA":"United States Of America"
]

if let a = lookUp( "JO",dictionary:dict ) {
    print(a) // prints Jordan 
} else {
    print("cant find value")
}

我已经重写了以下代码,但这一次,使用错误处理,保护语句,删除-> T?并编写符合ErrorType的枚举:

enum lookUpErrors : ErrorType {
    case noSuchKeyInDictionary
}

func lookUpThrows<T:Equatable>(key:T , dic:[T:T])throws {
    for i in dic.keys{
        guard i == key else {
            throw lookUpErrors.noSuchKeyInDictionary
        }
        print(dic[i]!)   
    }
}

do {
    try lookUpThrows("UAE" , dic:dict) // prints united arab emirates
}
catch lookUpErrors.noSuchKeyInDictionary{
    print("cant find value")
}

这两个功能都很好但是:

  • 哪个功能可以提供更好的性能

  • 哪个功能更安全&#34;

  • 推荐使用哪种功能(基于利弊)

3 个答案:

答案 0 :(得分:19)

效果

这两种方法应该具有可比性。在引擎盖下,它们都做了非常相似的事情:返回一个带有被检查标志的值,并且只有当标志显示结果有效时才继续。对于选项,该标志是枚举(.None vs .Some),throws该标志是一个隐式触发跳转到catch块的标志。

值得注意的是,你的两个函数不做同样的事情(如果没有键匹配则返回nil,如果第一个键不匹配则返回另一个)。 / p>

如果性能至关重要,那么你可以通过消除不必要的键下标查找来编写它以便更快地运行:

func lookUp<T:Equatable>(key:T , dictionary:[T:T]) -> T?  {
    for (k,v) in dictionary where k == key {
        return v
    }
    return nil
}

func lookUpThrows<T:Equatable>(key:T , dictionary:[T:T]) throws -> T  {
    for (k,v) in dic where k == key {
        return v
    }
    throw lookUpErrors.noSuchKeyInDictionary
}

如果您在紧密循环中使用有效值对这两者进行基准测试,则它们的执行方式相同。如果您使用无效值对它们进行基准测试,则可选版本的速度大约是速度的两倍,因此可能实际上抛出的开销有一点点开销。但是,除非你真的在一个非常紧凑的循环中调用这个函数并预测很多失败,否则可能没有什么值得注意的。

哪个更安全?

他们都是相同安全的。在任何情况下都不能调用该函数,然后意外地使用无效结果。编译器强制您解包可选项,或者捕获错误。

在这两种情况下,您都可以绕过安全检查:

// force-unwrap the optional
let name = lookUp( "JO", dictionary: dict)!
// force-ignore the throw
let name = try! lookUpThrows("JO" , dic:dict)

这实际上归结为强迫呼叫者处理可能的故障的哪种方式更可取。

建议使用哪种功能?

虽然这更主观,但我认为答案非常明确。你应该使用可选的而不是投掷的。

对于语言风格指导,我们只需要查看标准库。 Dictionary已经有一个基于键的查找(这个函数重复),它返回一个可选的。

可选择的更好选择的一个重要原因是,在这个函数中,只有一个可能出错。返回nil时,仅出于一个原因,即字符中不存在该键。在任何情况下,函数都不需要指出它抛出的几个原因,并且返回nil的原因应该对调用者来说是完全明显的。

另一方面,如果有多种原因,并且函数可能需要返回解释(例如,执行网络调用的函数,可能因网络故障或数据损坏而失败),则错误分类失败,可能包括一些错误文本将是一个更好的选择。

在这种情况下,可选的更好的另一个原因是失败甚至可能是预期/常见的。异常/意外故障的错误更多。返回可选项的好处是可以非常轻松地使用其他可选功能来处理它 - 例如可选链接(lookUp("JO", dic:dict)?.uppercaseString)或使用nil-coalescing(lookUp("JO", dic:dict) ?? "Team not found")进行默认。相比之下,try/catch设置和使用有点痛苦,除非调用者真的想要“异常”错误处理,即要做一堆东西,其中一些可能会失败,但是想要收集在底部处理故障。

答案 1 :(得分:7)

@AirspeedVelocity已经有了一个很好的答案,但我认为值得进一步研究为什么使用选项和错误。

基本上有四种方法可以解决问题:

  • 简单错误:它只以一种方式失败,因此您不需要关心为什么出错了。问题可能来自程序员逻辑或用户数据,因此您需要能够在运行时处理它并在编码时围绕它进行设计。

    Int初始化String(字符串可解析为整数或不是)或字典式查找(在那里&#)就是这种情况。 39;密钥的值或者不是)。可选项在Swift中非常适用。

  • 逻辑错误:这是一种错误(理论上)只在开发过程中出现,因为做错了 - 例如,索引超出了数组的范围。

    在ObjC中,NSException涵盖了这些案例。在Swift中,我们有像fatalError这样的函数。我假设为什么NSException没有在Swift中浮出水面的部分原因是,一旦你的程序遇到逻辑错误,对它的进一步操作假设不是很安全。逻辑错误应该在开发期间捕获或导致(可良好调试)崩溃,而不是让程序继续处于未定义(因此不安全)的状态。

  • 通用错误:有许多方法可以失败,但它们与程序员逻辑或用户操作无关。你可能会耗尽内存来分配,得到一个低级别的中断,或者(等待它......)溢出堆栈,但是这几乎可以在你做的任何事情中发生,而不是因为你做的任何具体事情。< / p>

    你会看到普遍的错误在某些其他语言中被表现为异常,但这意味着你必须围绕你所做的任何一次调用失败的可能性进行编码。此时,您正在编写比实际代码更多的错误处理。

  • 可恢复错误:这是因为有很多方法可以解决问题,但不会妨碍进一步的操作,程序在遇到错误时所做的事情可能会根据错误的类型而改变。文件系统和网络是这里的常见示例:如果您无法加载文件,可能是因为用户输入的名称错误(因此您应该告诉用户)或因为wifi暂时丢失并且很快就会回来(所以你可以放弃警报,然后再试一次)。

    在Cocoa中,历史上,这是NSError参数的用途。 Swift的错误处理使这种模式成为语言的一部分。

因此,当您在Swift中编写新的API(为自己或其他人调用)时,或使用新的ObjC注释使Swift中的现有API更易于使用时,请考虑您的错误类型&# 39;重新处理。

  • 是否只有一种明显的失败方法,即API滥用的结果?使用可选的返回类型。

  • 只有当客户没有遵循您的API合同时才会失败 - 例如,如果您正在编写具有下标和count的容器类,或者要求一些特定的呼叫顺序?如果您的Swift API是ObjC的前端,请不要为使用您的API的每一段代码负担错误处理或可选的解包 - 只需fatalErrorassert(或抛出NSException代码)并记录人们使用您的API的正确方法。

  • 好的,所以你的ObjC init方法返回nil iff [super init]返回nil。那么你应该将你的初始化程序标记为可以使用Swift还是添加错误超出parpameter?考虑一下这种情况何时发生 - 如果-[NSObject init]正在返回nil,那么因为你将alloc调用返回nil而将其链接起来。如果alloc失败,那么您的流程已经是“结束时间”,因此不值得处理此案例。

  • 您是否有多个失败案例,其中部分或全部可能值得向用户报告?或者,调用您的API的客户可能想要忽略一些但不是全部?编写一个throws和一组相应的ErrorType值的Swift函数,或一个返回NSError out参数的ObjC方法。

答案 2 :(得分:4)

如果您使用的是Swift 2.0,则可以使用这两个版本。第二个版本使用try / catch(与2.0一起引入),因此不向后兼容,这可能是一个不利于考虑的问题。

如果有任何性能差异,那么它将被忽略。

我个人最喜欢的是第一个,因为它很直接且可读性很好。如果我不得不对第二个版本进行维护,我会问自己为什么作者对这样一个简单的案例采用了try / catch方法。所以我宁愿感到困惑......

如果你有许多具有许多出口点(抛出)的复杂条件,那么我会选择第二个。但正如我所说,情况并非如此。