Подтвердить что ты не робот

Как я могу сказать, какая инструкция охраны не удалась?

Если у Ive есть куча прикованных инструкций 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-мя сообщениями был неудачным и вызвал блокировку 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 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.

Какой идиоматический способ Быстро?

4b9b3361

Ответ 1

Эрика Садун просто написала хорошее сообщение в блоге по этой точной теме.

Ее решение состояло в том, чтобы приветствовать предложение 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 {
        // ...
}

Эрика написать эту тему можно здесь здесь

Ответ 2

Обычно оператор guard не позволяет вам различать, какое из его условий не выполнялось. Его цель состоит в том, что, когда программа выполняет предыдущий операнд, вы знаете, что все переменные не равны нулю. Но он не дает никаких значений внутри тела guard/else (вы просто знаете, что условия не все удовлетворены).

Тем не менее, если все, что вы хотите сделать, - это print что-то, когда один из шагов возвращает nil, вы можете использовать оператор коалесценции ?? для выполнения дополнительного действия.

Создайте общую функцию, которая печатает сообщение и возвращает 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
}

Затем используйте эту функцию как "резервную" для каждого случая. Поскольку оператор ?? использует оценку короткого замыкания, правая часть не будет выполняться, если только левая сторона не вернула нуль.

guard
    let keypath = dictionary["field"] as? String ?? printAndFail("missing keypath"),
    let rule = dictionary["rule"] as? String ?? printAndFail("missing rule"),
    let comparator = FormFieldDisplayRuleComparator(rawValue: rule) ?? printAndFail("missing comparator"),
    let value = dictionary["value"] ?? printAndFail("missing value")
else
{
    // ...
    return
}

Ответ 3

Один возможный (неидиоматический) обходной путь: используйте предложение where для отслеживания успеха каждой последующей необязательной привязки в блоке 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

Выше мы используем тот факт, что результатом присваивания является пустой набор (), тогда как побочным эффектом является присвоение lhs выражения.

Если вы не хотите вводить изменяемый счетчик i перед областью действия guard, вы можете поместить счетчик и его приращение как статический член класса, например

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)")
}

Я должен, вероятно, указать, однако, что выше, вероятно, ближе к категории "хакерская" конструкция, а не к идиоматическому.

Ответ 4

Простейшая вещь, о которой я могу думать, состоит в том, чтобы вывести утверждения в 4 последовательных выражения guard else, но это неправильно.

По моему личному мнению, метод 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
}

Ответ 5

Очень хороший вопрос

Мне жаль, что у меня не было хорошего ответа, но я этого не делал.

Пусть начнется

Однако рассмотрим проблему вместе. Это упрощенная версия вашей функции.

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 мы делаем NOT доступ к константам, определенным в инструкции guard. Это связано с тем, что компилятор не знает, какое из пунктов было выполнено. Таким образом, это предполагает худший сценарий, когда первое предложение не срабатывало.

Заключение: мы не можем написать "простую" проверку внутри оператора else, чтобы понять, что не работает.

Написание комплексной проверки внутри else

Конечно, мы могли бы реплицировать внутри else логику, которую мы поместили insito в инструкцию охраны, чтобы узнать, что произошло, но этот шаблонный код очень уродлив и нелегко поддерживать.

Beyond noil: ошибки при метании

Итак, да, нам нужно разделить инструкцию охраны. Однако, если нам нужна более подробная информация о том, что пошло не так, наша функция foo больше не должна возвращать значение 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
}

Мне интересно, что сообщество думает об этом.

Ответ 6

Я думаю, что другие ответы здесь лучше, но другой подход заключается в определении таких функций:

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)")

Вы можете расширить его, чтобы распечатать файл и номер строки инструкции охраны, как и другие ответы.

С положительной стороны на сайте вызова не слишком много помех, и вы получаете только выход для неудачных случаев. Но поскольку он использует кортежи, и вы не можете написать функцию, которая работает с произвольными кортежами, вам нужно будет определить аналогичный метод для одного параметра, два параметра и т.д. До некоторой арности. Он также нарушает визуальную связь между предложением и переменной, к которой он привязан, особенно если развернутые предложения длинны.