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

Если у меня есть куча цепных операторов, как я могу диагностировать, какое условие не удалось, если не разбить мою охрану на несколько операторов?

Учитывая этот пример:

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 последовательных защитных оператора 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
    }

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

Что за идиоматический способ Свифта?

 immibis06 июн. 2016 г., 22:33
@jtbandes Устранить проблему, возможно?
 Darren06 июн. 2016 г., 23:31
print ("Плохой словарь: \ (словарь)")
 dfri06 июн. 2016 г., 21:34
@jtbandes Я думаю, что ваше решение с нулевым слиянием столь же жизнеспособно (обходной путь), как и остальные наши ниже; рассмотреть вопрос о добавлении его в качестве ответа (к этому несколько интересному Q & A :)?
 jtbandes06 июн. 2016 г., 20:29
Вы можете сделать свою собственную функцию, такую ​​какfunc printAndFail<T>(message: String) -> T? { print(message); return nil } а затем использоватьguard let keypath = ... ?? printAndFail("keypath")
 jtbandes06 июн. 2016 г., 20:27
Почему ты хочешь знать это? Что вы планируете делать с информацией?
 Moshe06 июн. 2016 г., 20:28
Я пытаюсь диагностировать входные данные / источник данных, поскольку на данный момент я редактирую их вручную.
 jtbandes06 июн. 2016 г., 21:36
Конечно, я переведу это в ответ.

Ответы на вопрос(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)")

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

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

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

о которой я могу подумать, - это разбить операторы на 4 последовательных защитных оператора 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
}
Очень хороший вопрос

Давай начнем

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

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

Вывод: мы не можем написать «простой» чек внутриelse Заявление, чтобы понять, что не работает.

Написание сложного чека внутри остального

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

За пределами нуля: бросать ошибки

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

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

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

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

Решение Вопроса

у.

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

Эрика рецензии на эту тему можно найтиВот

 Moshe09 июн. 2016 г., 16:40
Да, я спросил Эрику, как справиться с этим в то же время, когда отправлял этот вопрос. Это то, что я использую, хотя ответ @jtbandes похож и, вероятно, одинаково действителен.
 Lee Andrew29 авг. 2018 г., 08:36
Я попытался это сделать, но компилятору не понравилось, где «Diagnose ()» сразу после проверки, в конечном итоге с помощью Erica Diagnose (), чтобы получить файл и строки с «??» jtbandes предложение. Есть ли шанс, что я могу также вывести имя переменной, которая была равна нулю в функции диагностики? Причина в том, что мне не нужно полагаться на линию (которая может легко измениться), чтобы найти проблему
 jtbandes06 июн. 2016 г., 22:59
Интересное сравнение. Это используетwhere а такжеreturn true аналогично тому, как использует мой ответ?? а такжеreturn nil.

Ваш ответ на вопрос