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

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

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

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

Но проблема с ранним возвратом состоит в том, что вы получаете как минимум две точки выхода (потому что 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 сделал оператор defer способным перехватить возвращаемое значение, не так ли?

Это сделало бы отладку намного проще, в то же время используя 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
}

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
}
Другие вопросы по тегам