考虑我编写的以下查找函数,即使用选项和可选绑定,如果在字典中找不到键,则报告消息
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")
}
Run Code Online (Sandbox Code Playgroud)
我已经重写了下面的代码,但这一次,使用错误处理,保护语句,删除-> 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")
}
Run Code Online (Sandbox Code Playgroud)
两种功能都运行良好,但:
哪个功能可以提供更好的性能
哪个功能"更安全"
建议使用哪种功能(基于优缺点)
Air*_*ity 20
这两种方法应该具有可比性.在引擎盖下,它们都做了非常相似的事情:返回一个带有被检查标志的值,并且只有当标志显示结果有效时才继续.对于选项,该标志是枚举(.None vs .Some),throws
该标志是一个隐式触发跳转到catch块的标志.
值得注意的是,你的两个函数不做同样的事情(nil
如果没有键匹配则返回一个,如果第一个键不匹配则返回另一个).
如果性能至关重要,那么你可以通过消除不必要的键下标查找来编写它以便更快地运行:
func lookUp<T:Equatable>(key:T , dictionary:[T:T]) -> T? {
for (k,v) in dictionary where k == key {
return v
}
return nil
}
Run Code Online (Sandbox Code Playgroud)
和
func lookUpThrows<T:Equatable>(key:T , dictionary:[T:T]) throws -> T {
for (k,v) in dic where k == key {
return v
}
throw lookUpErrors.noSuchKeyInDictionary
}
Run Code Online (Sandbox Code Playgroud)
如果您在紧密循环中使用有效值对这两者进行基准测试,则它们的执行方式相同.如果您使用无效值对它们进行基准测试,则可选版本的速度大约是速度的两倍,因此可能实际上抛出的开销有一点点开销.但是,除非你真的在一个非常紧凑的循环中调用这个函数并预测很多失败,否则可能没有什么值得注意的.
他们都是相同的安全.在任何情况下都不能调用该函数,然后意外地使用无效结果.编译器强制您解包可选项或捕获错误.
在这两种情况下,您都可以绕过安全检查:
// force-unwrap the optional
let name = lookUp( "JO", dictionary: dict)!
// force-ignore the throw
let name = try! lookUpThrows("JO" , dic:dict)
Run Code Online (Sandbox Code Playgroud)
这实际上归结为强制呼叫者处理可能的故障的哪种方式更可取.
虽然这更主观,但我认为答案非常明确.你应该使用可选的而不是投掷的.
对于语言风格指导,我们只需要查看标准库.Dictionary已经有一个基于键的查找(这个函数重复),它返回一个可选的.
可选择的更好选择的一个重要原因是,在这个函数中,只有一件事可能出错.当nil
返回,它是只有一个原因,那就是,关键是不存在的字典.在任何情况下,函数都不需要指出它抛出的几个原因,并且nil
返回的原因对于调用者来说应该是完全明显的.
另一方面,如果有多种原因,并且函数可能需要返回解释(例如,执行网络调用的函数,可能因网络故障或数据损坏而失败),则会出现错误分类失败,可能包括一些错误文本将是一个更好的选择.
在这种情况下,可选的更好的另一个原因是失败甚至可能是预期/常见的.异常/意外故障的错误更多.返回可选项的好处是可以非常轻松地使用其他可选功能来处理它 - 例如lookUp("JO", dic:dict)?.uppercaseString
使用nil-coalescing(lookUp("JO", dic:dict) ?? "Team not found"
)进行可选链接()或默认.相比之下,try/catch
设置和使用有点痛苦,除非调用者真的想要"异常"错误处理,即要做一堆东西,其中一些可能会失败,但是想要收集失败处理底部.
@AirspeedVelocity已经有了一个很好的答案,但我认为值得进一步研究为什么使用选项与错误.
出现问题基本上有四种方法:
简单错误:它只在一种方式失败,所以你不需要关心为什么出错了.问题可能来自程序员逻辑或用户数据,因此您需要能够在运行时处理它并在编码时围绕它进行设计.
这就是Int
从初始化a String
(或者字符串是可解析为整数或不是)或字典式查找(或者是键的值或者没有)的情况.Optionals在Swift中非常适合.
逻辑错误:这是(理论上)仅在开发过程中出现的错误,例如,做错了 - 例如,索引超出了数组的范围.
在ObjC中,NSException
涵盖了这些案例.在Swift中,我们有类似的功能fatalError
.我假设NSException
Swift没有浮出水面的部分原因是,一旦你的程序遇到逻辑错误,假设它的进一步操作并不是真的安全.逻辑错误应该在开发期间捕获或导致(可良好调试)崩溃,而不是让程序继续处于未定义(因此不安全)的状态.
通用错误:存在多种失败方法,但它们与程序员逻辑或用户操作之间没有很大关联.你可能会耗尽内存来分配,得到一个低级别的中断,或者(等待它......)溢出堆栈,但是这些几乎可以在你做的任何事情中发生,而不是因为你做的任何具体事情.
您会看到普遍错误在某些其他语言中表现为异常,但这意味着您必须围绕您所做的任何和每个调用都能够失败的可能性进行编码.那时你正在编写比实际代码更多的错误处理.
可恢复的错误:这是因为有很多方法可以解决问题,但不会妨碍进一步的操作,程序在遇到错误时所做的事情可能会根据错误的类型而改变.文件系统和网络是这里的常见示例:如果您无法加载文件,可能是因为用户输入了错误的名称(因此您应该告诉用户)或因为wifi暂时丢失并且很快就会回来(所以你可能会放弃警报,然后再试一次).
在Cocoa中,历史上,这就是NSError
参数的用途.Swift的错误处理使这种模式成为语言的一部分.
因此,当您在Swift中编写新的API(为自己或其他人调用)时,或使用新的ObjC注释使Swift中的现有API更易于使用时,请考虑您正在处理的错误类型.
是否只有一种明确的失败方法不是API误用的结果?使用可选的返回类型.
只有在客户不遵守您的API合同时才会失败 - 例如,如果您正在编写一个具有下标和a的容器类count
,或者要求进行某些特定的调用序列?不要将使用API的每一段代码都加载错误处理或可选的解包 - 只是fatalError
或assert
(NSException
如果您的Swift API是ObjC代码的前端,则抛弃)并记录人们使用API的正确方法.
好的,所以你的ObjC init
方法返回nil iff [super init]
返回nil.那么你应该将你的初始化程序标记为可以使用Swift还是添加错误超出parpameter?想想何时真的发生了 - 如果-[NSObject init]
返回nil,那是因为你把它链接到一个alloc
返回nil 的调用.如果alloc
失败,它已经是你的过程的结束时间,所以不值得处理这种情况.
您是否有多个失败案例,其中部分或全部可能值得向用户报告?或者,调用您的API的客户可能想要忽略一些但不是全部?编写一个Swift函数throws
和一组相应的ErrorType
值,或者一个返回NSError
out参数的ObjC方法.