Swift: упростите отладку, имея возможность перехватывать возвращаемое значение в операторе отсрочки.

Поэтому мне нравится объявлять переменные для удержания возвращаемого значения, а затем возвращать указанную переменную в следующей строке, что упрощает отладку моего кода. Я могу просто установить точку останова в строке возврата и посмотреть, какое значение она возвращает. Я использую это везде, и это значительно упрощает отладку всего моего кода.

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let cellCount = models.count
    return cellCount
}

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

Но проблема с ранним возвратом заключается в том, что вы получаете по крайней мере две точки выхода (поскольку guard требует return в этом контексте) от вашего метода, что затрудняет отладку.

// Instantiate using dependency injection
private let reachability: ReachabilityProtocol
private let apiClient: APIClientProtocol

    // Returns true if could start login request, else false 
    func loginUser(username: String, password: String) -> Bool {
        defer {
             // Not possible, would be nice! Return value would be an implicitly declared variable
             // exaclty like the variables 'newValue' and 'oldValue' in property observers!
            print("return value: \(returnValue)")
        }
        guard reachability.isOnline && !username.isEmpty && !password.isEmpty { return false }
        apiClient.loginUser(username, password: password)
        return true
    }

Было бы здорово, если бы Swift 3.X сделал инструкция отсрочки способна перехватывать возвращаемое значение, не так ли?

Это значительно упростит отладку, но при этом будет использоваться guard и ранние возвраты. У меня нет понимания того, что такое когда-либо писать компиляторы и т. д., но мне кажется, что это не будет так уж сложно реализовать в следующих версиях Swift?

Можете ли вы придумать другой способ получения одной точки для чтения возвращаемого значения метода с несколькими точками выхода? (Не дожидаясь предложенного мною улучшения до defer?)

Изменить:
Мой пример с логином выше не идеален, простите, зачем мне писать такой код? Ха-ха! Но есть много других подобных сценариев, может быть, что-то вроде этого, использование do-try-catch также затрудняет отладку кода:

// We don't know the return value of this function! Makes it hard to debug!
func fetchUserByFirstName(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {
    defer {
         // Not possible, would be nice! Return value would be an implicitly declared variable
         // exaclty like the variables 'newValue' and 'oldValue' in property observers!
        print("return value: \(returnValue)")
    }

    guard !firstName.isEmpty else { print("firstName can't be empty"); return nil }
    guard !lastName.isEmpty else { print("lastName can't be empty"); return nil }
    // Imagine we have some kind of debug user... Does not really make sense, but good for making a point.
    guard firstName != "DEBUG" else { return User.debugUser }
    let fetchRequest = NSFetchRequest(entityName: Users.entityName)
    let predicate = NSPredicate(format: "firstName == \(firstName) AND lastName == \(lastName)")
    fetchRequest.predicate = predicate
    do {
        let user = try context.executeFetchRequest(fetchRequest)
        return user
    } catch let error as NSError {
        print("Error fetching user: \(error)")
    }
    return nil
}

person Sajjon    schedule 15.07.2016    source источник


Ответы (1)


Мне нравится ваше предложенное улучшение Swift, чтобы defer зафиксировать возвращаемое значение.

Вот что-то, что будет работать, но оно не идеально, потому что требует небольшой дополнительной работы (и беспорядка в коде), но вы можете сделать это вручную, объявив returnValue с let в верхней части вашей функции, придав ей тот же тип, что и функция возвращается. Затем замените все ваши return <something> на returnValue = <something>; return returnValue.

Объявляя returnValue с let, Swift сообщит вам, если вы забудете назначить returnValue перед выходом из функции. Поэтому, если вы добавите в свою функцию новый return, ваша функция не скомпилируется, пока вы не назначите returnValue. Вы увидите сообщение об ошибке: ошибка: константа returnValue использовалась до инициализации.

func fetchUserByFirstName(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {
    let returnValue: User?
    defer {
        print("return value: \(returnValue)")
    }

    guard !firstName.isEmpty else { print("firstName can't be empty"); returnValue = nil; return returnValue }
    guard !lastName.isEmpty else { print("lastName can't be empty"); returnValue = nil; return returnValue }
    // Imagine we have some kind of debug user... Does not really make sense, but good for making a point.
    guard firstName != "DEBUG" else { returnValue = User.debugUser; return returnValue }
    let fetchRequest = NSFetchRequest(entityName: Users.entityName)
    let predicate = NSPredicate(format: "firstName == \(firstName) AND lastName == \(lastName)")
    fetchRequest.predicate = predicate
    do {
        let user = try context.executeFetchRequest(fetchRequest)
        returnValue = user; return returnValue
    } catch let error as NSError {
        print("Error fetching user: \(error)")
    }
    returnValue = nil; return returnValue
}

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

func fetchUserByFirstName(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {

    func innerFunc(firstName: String, andLastName lastName: String, fromContext context: NSManagedObjectContext) -> User? {

        guard !firstName.isEmpty else { print("firstName can't be empty"); return nil }
        guard !lastName.isEmpty else { print("lastName can't be empty"); return nil }
        // Imagine we have some kind of debug user... Does not really make sense, but good for making a point.
        guard firstName != "DEBUG" else { return User.debugUser }
        let fetchRequest = NSFetchRequest(entityName: Users.entityName)
        let predicate = NSPredicate(format: "firstName == \(firstName) AND lastName == \(lastName)")
        fetchRequest.predicate = predicate
        do {
            let user = try context.executeFetchRequest(fetchRequest)
            return user.first as? User
        } catch let error as NSError {
            print("Error fetching user: \(error)")
        }
        return nil
    }

    let returnValue = innerFunc(firstName, andLastName: lastName, fromContext: context)
    print("return value: \(returnValue)")
    return returnValue
}
person vacawama    schedule 15.07.2016
comment
Да именно то, что я обычно делаю! :) спасибо, что нашли время и предложили это! :) Думаю, это единственный способ на данный момент... - person Sajjon; 15.07.2016
comment
Добавлено второе решение, которое хуже ;-) - person vacawama; 15.07.2016
comment
Ух ты! Я даже не знал, что можно объявить функцию внутри функции! Это очень креативные решения! - person Sajjon; 15.07.2016