我如何判断哪个守卫声明失败了?
How do I tell which guard statement failed?
如果我有一堆链式 guard let 语句,除了将我的 guard let 分解成多个语句外,我如何诊断哪个条件失败?
给出这个例子:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
return nil
}
如何判断 4 个 let 语句中的哪一个失败并调用了 else 块?
我能想到的最简单的事情是将语句分解为 4 个连续的 guard else 语句,但感觉不对。
guard let keypath = dictionary["field"] as? String
else
{
print("Keypath failed to load.")
self.init()
return nil
}
guard let rule = dictionary["rule"] as? String else
{
print("Rule failed to load.")
self.init()
return nil
}
guard let comparator = FormFieldDisplayRuleComparator(rawValue: rule) else
{
print("Comparator failed to load for rawValue: \(rule)")
self.init()
return nil
}
guard let value = dictionary["value"] else
{
print("Value failed to load.")
self.init()
return nil
}
如果我想将它们全部放在一个 guard 语句中,我可以想到另一种选择。检查 guard 语句中的 nils 可能有效:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
if let keypath = keypath {} else {
print("Keypath failed to load.")
}
// ... Repeat for each let...
return nil
}
我什至不知道这是否会编译,但我还不如使用一堆 if let
语句或 guard
开始。
惯用的 Swift 方式是什么?
The simplest thing I can think of is to break out the statements into 4 sequential guard else statements, but that feels wrong.
在我个人看来,Swift 方式不应该要求您检查值是否为 nil
。
但是,您可以扩展 Optional
以满足您的需要:
extension Optional
{
public func testingForNil<T>(@noescape f: (Void -> T)) -> Optional
{
if self == nil
{
f()
}
return self
}
}
允许:
guard let keypath = (dictionary["field"] as? String).testingForNil({ /* or else */ }),
let rule = (dictionary["rule"] as? String).testingForNil({ /* or else */ }),
let comparator = FormFieldDisplayRuleComparator(rawValue: rule).testingForNil({ /* or else */ }),
let value = dictionary["value"].testingForNil({ /* or else */ })
else
{
return nil
}
一种可能的(非惯用的)解决方法:使用 where
子句来跟踪 guard
块
中每个后续可选绑定的成功
我认为将您的 guard 语句拆分为单独的 guard 块没有任何问题,以防您对哪个 guard 语句失败感兴趣。
然而,从技术角度来看,分隔 guard
块的一种替代方法是使用 where
子句(针对每个可选绑定)在每次可选绑定时递增计数器是成功的。如果绑定失败,计数器的值可用于跟踪这是哪个绑定。例如:
func foo(a: Int?, _ b: Int?) {
var i: Int = 1
guard let a = a where (i+=1) is (),
let b = b where (i+=1) is () else {
print("Failed at condition #\(i)")
return
}
}
foo(nil,1) // Failed at condition #1
foo(1,nil) // Failed at condition #2
上面我们利用了赋值的结果是空元组()
,而副作用[=37] =] 是对表达式的 lhs 的赋值。
如果您想避免在 guard
子句的范围之前引入可变计数器 i
,您可以将计数器及其递增作为静态 class成员,例如
class Foo {
static var i: Int = 1
static func reset() -> Bool { i = 1; return true }
static func success() -> Bool { i += 1; return true }
}
func foo(a: Int?, _ b: Int?) {
guard Foo.reset(),
let a = a where Foo.success(),
let b = b where Foo.success() else {
print("Failed at condition #\(Foo.i)")
return
}
}
foo(nil,1) // Failed at condition #1
foo(1,nil) // Failed at condition #2
可能更自然的方法是通过让函数抛出错误来传播计数器的值:
class Foo { /* as above */ }
enum Bar: ErrorType {
case Baz(Int)
}
func foo(a: Int?, _ b: Int?) throws {
guard Foo.reset(),
let a = a where Foo.success(),
let b = b where Foo.success() else {
throw Bar.Baz(Foo.i)
}
// ...
}
do {
try foo(nil,1) // Baz error: failed at condition #1
// try foo(1,nil) // Baz error: failed at condition #2
} catch Bar.Baz(let num) {
print("Baz error: failed at condition #\(num)")
}
不过,我可能应该指出,上述内容可能更接近于归类为“hacky”结构,而不是惯用结构。
很好的问题
我希望对此有一个好的答案,但我没有。
让我们开始吧
不过还是一起来看看问题吧。这是您的函数的简化版本
func foo(dictionary:[String:AnyObject]) -> AnyObject? {
guard let
a = dictionary["a"] as? String,
b = dictionary[a] as? String,
c = dictionary[b] else {
return nil // I want to know more ☹️ !!
}
return c
}
在 else 里面我们不知道哪里出了问题
首先,在 else
块中,我们 NOT 可以访问 guard
语句中定义的常量。这是因为编译器不知道哪个子句失败了。所以它确实假设了第一个子句失败的最坏情况。
结论:我们不能在 else
语句中编写 "simple" 检查来了解什么不起作用。
在 else 中编写一个复杂的检查
当然我们可以在 else
中复制我们放入 guard 语句中的逻辑,以找出确实失败的子句,但是这个样板代码非常丑陋且不易维护。
超过零:抛出错误
所以是的,我们需要拆分 guard 语句。但是,如果我们想要更详细的信息,了解哪里出了问题,我们的 foo
函数不应再 return 一个 nil
值来表示错误,它应该抛出一个错误。
所以
enum AppError: ErrorType {
case MissingValueForKey(String)
}
func foo(dictionary:[String:AnyObject]) throws -> AnyObject {
guard let a = dictionary["a"] as? String else { throw AppError.MissingValueForKey("a") }
guard let b = dictionary[a] as? String else { throw AppError.MissingValueForKey(a) }
guard let c = dictionary[b] else { throw AppError.MissingValueForKey(b) }
return c
}
我很好奇社区对此有何看法。
通常,guard
语句不会让您区分它的哪些条件不满足。它的目的是当程序执行past guard语句时,你知道所有的变量都是非nil的。但是它没有提供任何值 inside guard/else
body(你只知道条件没有全部满足)。
就是说,如果您想要做的只是 print
某些步骤 returns nil
,您可以使用合并运算符 ??
执行额外的操作。
创建一个打印消息的通用函数 returns nil
:
/// Prints a message and returns `nil`. Use this with `??`, e.g.:
///
/// guard let x = optionalValue ?? printAndFail("missing x") else {
/// // ...
/// }
func printAndFail<T>(message: String) -> T? {
print(message)
return nil
}
然后将此函数用作每个案例的 "fallback"。由于 ??
运算符使用 short-circuit evaluation,除非左侧已经返回 nil,否则不会执行右侧。
guard
let keypath = dictionary["field"] as? String <b>?? printAndFail("missing keypath"),</b>
let rule = dictionary["rule"] as? String <b>?? printAndFail("missing rule"),</b>
let comparator = FormFieldDisplayRuleComparator(rawValue: rule) <b>?? printAndFail("missing comparator"),</b>
let value = dictionary["value"] <b>?? printAndFail("missing value")</b>
else
{
// ...
return
}
Erica Sadun 刚刚就这个主题写了一篇不错的博客 post。
她的解决方案是劫持 where
子句并使用它来跟踪哪些保护语句通过。使用 diagnose
方法的每个成功的保护条件都会将文件名和行号打印到控制台。最后一个 diagnose
打印语句之后的警戒条件是失败的条件。解决方案如下所示:
func diagnose(file: String = #file, line: Int = #line) -> Bool {
print("Testing \(file):\(line)")
return true
}
// ...
let dictionary: [String : AnyObject] = [
"one" : "one"
"two" : "two"
"three" : 3
]
guard
// This line will print the file and line number
let one = dictionary["one"] as? String where diagnose(),
// This line will print the file and line number
let two = dictionary["two"] as? String where diagnose(),
// This line will NOT be printed. So it is the one that failed.
let three = dictionary["three"] as? String where diagnose()
else {
// ...
}
可以找到 Erica 关于此主题的文章 here
我认为这里的其他答案更好,但另一种方法是这样定义函数:
func checkAll<T1, T2, T3>(clauses: (T1?, T2?, T3?)) -> (T1, T2, T3)? {
guard let one = clauses.0 else {
print("1st clause is nil")
return nil
}
guard let two = clauses.1 else {
print("2nd clause is nil")
return nil
}
guard let three = clauses.2 else {
print("3rd clause is nil")
return nil
}
return (one, two, three)
}
然后像这样使用
let a: Int? = 0
let b: Int? = nil
let c: Int? = 3
guard let (d, e, f) = checkAll((a, b, c)) else {
fatalError()
}
print("a: \(d)")
print("b: \(e)")
print("c: \(f)")
您可以扩展它以像其他答案一样打印保护语句的文件和行号。
从好的方面来说,调用站点没有太多混乱,您只会获得失败案例的输出。但是由于它使用元组并且您不能编写对任意元组进行操作的函数,因此您必须为一个参数、两个参数等定义一个类似的方法,直到达到某个数量。它还破坏了子句与其绑定到的变量之间的视觉关系,尤其是当展开的子句很长时。
我的两分钱:
由于 Swift 不允许我在 guard let 中添加 where
,我想出了这个解决方案:
func validate<T>(_ input: T?, file: String = #file, line: Int = #line) -> T? {
guard let input = input else {
print("Nil argument at \(file), line: \(line)")
return nil
}
return input
}
class Model {
let id: Int
let name: String
init?(id: Int?, name: String?) {
guard let id = validate(id),
let name = validate(name) else {
return nil
}
self.id = id
self.name = name
}
}
let t = Model(id: 0, name: "ok") // Not nil
let t2 = Model(id: 0, name: nil) // Nil
let t3 = Model(id: nil, name: "ok") // Nil
此代码可用于所有 guard 和 if 逻辑测试,如可选、bool 和 case 测试。它打印失败的逻辑测试行。
class GuardLogger {
var lastGoodLine: Int
var lineWithError: Int { lastGoodLine + 1 }
var file: String
var function: String
init(file: String = #file, function: String = #function, line: Int = #line) {
self.lastGoodLine = line
self.file = file
self.function = function
}
func log(line: Int = #line) -> Bool {
lastGoodLine = line
return true
}
func print() {
Swift.print([file, function, String(lineWithError)].joined(separator: " "))
}
}
let testBoolTrue = true
let testBoolFalse = false
let guardLogger = GuardLogger()
guard
testBoolTrue, guardLogger.log(),
let testOptionalBoolTrue = Optional(testBoolTrue), guardLogger.log(),
let selfIsViewController = self as? UIViewController, guardLogger.log(),
testBoolTrue == false, guardLogger.log() // this fails
else {
print(guardLogger.lastGoodLine)
fatalError()
}
如果我有一堆链式 guard let 语句,除了将我的 guard let 分解成多个语句外,我如何诊断哪个条件失败?
给出这个例子:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
return nil
}
如何判断 4 个 let 语句中的哪一个失败并调用了 else 块?
我能想到的最简单的事情是将语句分解为 4 个连续的 guard else 语句,但感觉不对。
guard let keypath = dictionary["field"] as? String
else
{
print("Keypath failed to load.")
self.init()
return nil
}
guard let rule = dictionary["rule"] as? String else
{
print("Rule failed to load.")
self.init()
return nil
}
guard let comparator = FormFieldDisplayRuleComparator(rawValue: rule) else
{
print("Comparator failed to load for rawValue: \(rule)")
self.init()
return nil
}
guard let value = dictionary["value"] else
{
print("Value failed to load.")
self.init()
return nil
}
如果我想将它们全部放在一个 guard 语句中,我可以想到另一种选择。检查 guard 语句中的 nils 可能有效:
guard let keypath = dictionary["field"] as? String,
let rule = dictionary["rule"] as? String,
let comparator = FormFieldDisplayRuleComparator(rawValue: rule),
let value = dictionary["value"]
else
{
if let keypath = keypath {} else {
print("Keypath failed to load.")
}
// ... Repeat for each let...
return nil
}
我什至不知道这是否会编译,但我还不如使用一堆 if let
语句或 guard
开始。
惯用的 Swift 方式是什么?
The simplest thing I can think of is to break out the statements into 4 sequential guard else statements, but that feels wrong.
在我个人看来,Swift 方式不应该要求您检查值是否为 nil
。
但是,您可以扩展 Optional
以满足您的需要:
extension Optional
{
public func testingForNil<T>(@noescape f: (Void -> T)) -> Optional
{
if self == nil
{
f()
}
return self
}
}
允许:
guard let keypath = (dictionary["field"] as? String).testingForNil({ /* or else */ }),
let rule = (dictionary["rule"] as? String).testingForNil({ /* or else */ }),
let comparator = FormFieldDisplayRuleComparator(rawValue: rule).testingForNil({ /* or else */ }),
let value = dictionary["value"].testingForNil({ /* or else */ })
else
{
return nil
}
一种可能的(非惯用的)解决方法:使用 where
子句来跟踪 guard
块
中每个后续可选绑定的成功
我认为将您的 guard 语句拆分为单独的 guard 块没有任何问题,以防您对哪个 guard 语句失败感兴趣。
然而,从技术角度来看,分隔 guard
块的一种替代方法是使用 where
子句(针对每个可选绑定)在每次可选绑定时递增计数器是成功的。如果绑定失败,计数器的值可用于跟踪这是哪个绑定。例如:
func foo(a: Int?, _ b: Int?) {
var i: Int = 1
guard let a = a where (i+=1) is (),
let b = b where (i+=1) is () else {
print("Failed at condition #\(i)")
return
}
}
foo(nil,1) // Failed at condition #1
foo(1,nil) // Failed at condition #2
上面我们利用了赋值的结果是空元组()
,而副作用[=37] =] 是对表达式的 lhs 的赋值。
如果您想避免在 guard
子句的范围之前引入可变计数器 i
,您可以将计数器及其递增作为静态 class成员,例如
class Foo {
static var i: Int = 1
static func reset() -> Bool { i = 1; return true }
static func success() -> Bool { i += 1; return true }
}
func foo(a: Int?, _ b: Int?) {
guard Foo.reset(),
let a = a where Foo.success(),
let b = b where Foo.success() else {
print("Failed at condition #\(Foo.i)")
return
}
}
foo(nil,1) // Failed at condition #1
foo(1,nil) // Failed at condition #2
可能更自然的方法是通过让函数抛出错误来传播计数器的值:
class Foo { /* as above */ }
enum Bar: ErrorType {
case Baz(Int)
}
func foo(a: Int?, _ b: Int?) throws {
guard Foo.reset(),
let a = a where Foo.success(),
let b = b where Foo.success() else {
throw Bar.Baz(Foo.i)
}
// ...
}
do {
try foo(nil,1) // Baz error: failed at condition #1
// try foo(1,nil) // Baz error: failed at condition #2
} catch Bar.Baz(let num) {
print("Baz error: failed at condition #\(num)")
}
不过,我可能应该指出,上述内容可能更接近于归类为“hacky”结构,而不是惯用结构。
很好的问题
我希望对此有一个好的答案,但我没有。
让我们开始吧
不过还是一起来看看问题吧。这是您的函数的简化版本
func foo(dictionary:[String:AnyObject]) -> AnyObject? {
guard let
a = dictionary["a"] as? String,
b = dictionary[a] as? String,
c = dictionary[b] else {
return nil // I want to know more ☹️ !!
}
return c
}
在 else 里面我们不知道哪里出了问题
首先,在 else
块中,我们 NOT 可以访问 guard
语句中定义的常量。这是因为编译器不知道哪个子句失败了。所以它确实假设了第一个子句失败的最坏情况。
结论:我们不能在 else
语句中编写 "simple" 检查来了解什么不起作用。
在 else 中编写一个复杂的检查
当然我们可以在 else
中复制我们放入 guard 语句中的逻辑,以找出确实失败的子句,但是这个样板代码非常丑陋且不易维护。
超过零:抛出错误
所以是的,我们需要拆分 guard 语句。但是,如果我们想要更详细的信息,了解哪里出了问题,我们的 foo
函数不应再 return 一个 nil
值来表示错误,它应该抛出一个错误。
所以
enum AppError: ErrorType {
case MissingValueForKey(String)
}
func foo(dictionary:[String:AnyObject]) throws -> AnyObject {
guard let a = dictionary["a"] as? String else { throw AppError.MissingValueForKey("a") }
guard let b = dictionary[a] as? String else { throw AppError.MissingValueForKey(a) }
guard let c = dictionary[b] else { throw AppError.MissingValueForKey(b) }
return c
}
我很好奇社区对此有何看法。
通常,guard
语句不会让您区分它的哪些条件不满足。它的目的是当程序执行past guard语句时,你知道所有的变量都是非nil的。但是它没有提供任何值 inside guard/else
body(你只知道条件没有全部满足)。
就是说,如果您想要做的只是 print
某些步骤 returns nil
,您可以使用合并运算符 ??
执行额外的操作。
创建一个打印消息的通用函数 returns nil
:
/// Prints a message and returns `nil`. Use this with `??`, e.g.:
///
/// guard let x = optionalValue ?? printAndFail("missing x") else {
/// // ...
/// }
func printAndFail<T>(message: String) -> T? {
print(message)
return nil
}
然后将此函数用作每个案例的 "fallback"。由于 ??
运算符使用 short-circuit evaluation,除非左侧已经返回 nil,否则不会执行右侧。
guard
let keypath = dictionary["field"] as? String <b>?? printAndFail("missing keypath"),</b>
let rule = dictionary["rule"] as? String <b>?? printAndFail("missing rule"),</b>
let comparator = FormFieldDisplayRuleComparator(rawValue: rule) <b>?? printAndFail("missing comparator"),</b>
let value = dictionary["value"] <b>?? printAndFail("missing value")</b>
else
{
// ...
return
}
Erica Sadun 刚刚就这个主题写了一篇不错的博客 post。
她的解决方案是劫持 where
子句并使用它来跟踪哪些保护语句通过。使用 diagnose
方法的每个成功的保护条件都会将文件名和行号打印到控制台。最后一个 diagnose
打印语句之后的警戒条件是失败的条件。解决方案如下所示:
func diagnose(file: String = #file, line: Int = #line) -> Bool {
print("Testing \(file):\(line)")
return true
}
// ...
let dictionary: [String : AnyObject] = [
"one" : "one"
"two" : "two"
"three" : 3
]
guard
// This line will print the file and line number
let one = dictionary["one"] as? String where diagnose(),
// This line will print the file and line number
let two = dictionary["two"] as? String where diagnose(),
// This line will NOT be printed. So it is the one that failed.
let three = dictionary["three"] as? String where diagnose()
else {
// ...
}
可以找到 Erica 关于此主题的文章 here
我认为这里的其他答案更好,但另一种方法是这样定义函数:
func checkAll<T1, T2, T3>(clauses: (T1?, T2?, T3?)) -> (T1, T2, T3)? {
guard let one = clauses.0 else {
print("1st clause is nil")
return nil
}
guard let two = clauses.1 else {
print("2nd clause is nil")
return nil
}
guard let three = clauses.2 else {
print("3rd clause is nil")
return nil
}
return (one, two, three)
}
然后像这样使用
let a: Int? = 0
let b: Int? = nil
let c: Int? = 3
guard let (d, e, f) = checkAll((a, b, c)) else {
fatalError()
}
print("a: \(d)")
print("b: \(e)")
print("c: \(f)")
您可以扩展它以像其他答案一样打印保护语句的文件和行号。
从好的方面来说,调用站点没有太多混乱,您只会获得失败案例的输出。但是由于它使用元组并且您不能编写对任意元组进行操作的函数,因此您必须为一个参数、两个参数等定义一个类似的方法,直到达到某个数量。它还破坏了子句与其绑定到的变量之间的视觉关系,尤其是当展开的子句很长时。
我的两分钱:
由于 Swift 不允许我在 guard let 中添加 where
,我想出了这个解决方案:
func validate<T>(_ input: T?, file: String = #file, line: Int = #line) -> T? {
guard let input = input else {
print("Nil argument at \(file), line: \(line)")
return nil
}
return input
}
class Model {
let id: Int
let name: String
init?(id: Int?, name: String?) {
guard let id = validate(id),
let name = validate(name) else {
return nil
}
self.id = id
self.name = name
}
}
let t = Model(id: 0, name: "ok") // Not nil
let t2 = Model(id: 0, name: nil) // Nil
let t3 = Model(id: nil, name: "ok") // Nil
此代码可用于所有 guard 和 if 逻辑测试,如可选、bool 和 case 测试。它打印失败的逻辑测试行。
class GuardLogger {
var lastGoodLine: Int
var lineWithError: Int { lastGoodLine + 1 }
var file: String
var function: String
init(file: String = #file, function: String = #function, line: Int = #line) {
self.lastGoodLine = line
self.file = file
self.function = function
}
func log(line: Int = #line) -> Bool {
lastGoodLine = line
return true
}
func print() {
Swift.print([file, function, String(lineWithError)].joined(separator: " "))
}
}
let testBoolTrue = true
let testBoolFalse = false
let guardLogger = GuardLogger()
guard
testBoolTrue, guardLogger.log(),
let testOptionalBoolTrue = Optional(testBoolTrue), guardLogger.log(),
let selfIsViewController = self as? UIViewController, guardLogger.log(),
testBoolTrue == false, guardLogger.log() // this fails
else {
print(guardLogger.lastGoodLine)
fatalError()
}