So I love declaring variables to hold return value and then return said variable on next line, thus making it easy to debug my code, I can just set a breakpoint at the return line and see what value it returns. I use this everywhere and it makes all my code so much easier to debug.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let cellCount = models.count
return cellCount
}
But then you have the scenario where you have optionals and different conditions that have to be met in order for your method to makes sense. The guard statement is great for making sure some conditions are met, while not introducing pyramids of doom.
But the problem with early returns is that you get at least two exit points (because guard
requires a return
in this context) from your method, which makes it harder to debug.
// 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
}
It would be awesome if Swift 3.X would make the defer statement able to catch the return value, wouldn't it?
This would make debugging so much easier, whilst still making use of guard
and early returns. I have not insight what so ever in writing compilers etc, but it feels like this would not be so hard to implement in coming versions of Swift?
Can you come up with a different way of achieving a single point to read return value of a method with multiple exit points? (Without having to wait for my suggested improvement to defer
?)
Edit:
My example above with the login is not a perfect example, sorry, why would I write code like that? Haha! But there are many other similar scenarios, maybe something like this, using do-try-catch
also makes code hard to debug:
// 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
}