Early return/golden path in Swift
Asked Answered
C

1

4

I'm used to write code with early return/golden path in Objective-C. I tried this approach in Swift, and noticed that early return comes at the expense of using the forced unwrapping operator (!) when optionals are involved.

Take a method that calculates the size of a directory. First, the golden path version:

private func calculateSize_GoldenPath(directory:String) -> UInt64 {
    let fileManager = NSFileManager.defaultManager()
    var error : NSError?
    var contents = fileManager.contentsOfDirectoryAtPath(directory, error: &error) as [String]?
    if contents == nil {
        NSLog("Failed to list directory with error \(error)")
        return 0
    }
    var size : UInt64 = 0
    for pathComponent in contents! {
        let path = directory.stringByAppendingPathComponent(pathComponent)
        let attributes : NSDictionary? = fileManager.attributesOfItemAtPath(path, error: &error)
        if (attributes == nil) {
            NSLog("Failed to read file size of \(path) with error \(error)")
            continue
        }
        size += attributes!.fileSize()
    }
    return size;
}

Notice how I'm using the ! operator both for the contents and attributes variables.

I'm assuming that overusing the ! operator kind of defeats the purpose of optionals and the type safety they bring. This is how I feel the above method should be coded in Swift to avoid forced unwrapping:

private func calculateSize_IfLet(directory:String) -> UInt64 {
    let fileManager = NSFileManager.defaultManager()
    var error : NSError?
    if let contents = fileManager.contentsOfDirectoryAtPath(directory, error: &error) as? [String] {
        var size : UInt64 = 0
        for pathComponent in contents {
            let path = directory.stringByAppendingPathComponent(pathComponent)
            if let attributes : NSDictionary = fileManager.attributesOfItemAtPath(path, error: &error) {
                size += attributes.fileSize()
            } else {
                NSLog("Failed to read file size of \(path) with error \(error)")
            }
        }
        return size
    } else {
        NSLog("Failed to list directory with error \(error)")
        return 0
    }
}

However, by using if let I can't do early return anymore. If some methods don't use early return and some do, then I end up with a project with mixed coding style.

My question is, is there a way to code in golden path style without resorting to forced unwrapping when optionals are involved?

Crate answered 25/8, 2014 at 11:17 Comment(5)
Yes, the "!" forced unwrapping operator is going to be over and inappropriately used in Swift code. While I like the "golden path" when combined with optionals and forced unwrapping the code is going to be prone to unwrapping errors on later updates possible by others. Personally I would use extraction of methods. P.S. +1 for the use of private!Indisputable
I hate to link rather than directly answer, but the answer winds up being long because it's a whole way of approaching the problem rather than a quick piece of code. But if you find it helpful: robnapier.net/functional-wish-fulfillment. See also nomothetis.svbtle.com/error-handling-in-swift which takes a very similar approach.Noctule
Nice article @RobNapier. What I take from it is that the golden path is an inherently imperative concept, and by fully embracing the functional aspect of Swift we can reach more appropriate solutions. Part of me wishes Swift had been released with its own system frameworks, much like Microsoft did with .net when they released C#.Crate
The funny thing is, what I'm hinting at with "continueWith" (and will explain more later) is actually not "fully embracing the functional." It's using a technique that brings imperative programming techniques to functional languages. Bizarrely backwards, I know, but quite powerful.Noctule
But yes, your basic thought is correct. Making use of Swift's features like enums with associated data and generics allows us to rethink how we manage errors. Rather than treating errors as a special condition to escape with, we treat them as just a result to be dealt with.Noctule
I
0

Personally I would use extraction of methods, in this case extract the pathComponent section into a separate method thus avoiding the multiple indent and awkward code that mashes conceptually separate code together.

private func calculateSize_IfLet(directoryPath:String) -> UInt64 {
    var size : UInt64 = 0
    let fileManager = NSFileManager.defaultManager()
    var error : NSError?
    if let contents = fileManager.contentsOfDirectoryAtPath(directoryPath, error: &error) as? [String] {
        size = self.calculateSizeOfDirectory(directoryPath, contents:contents)
    } else {
        NSLog("Failed to list directory with error \(error)")
    }
    return size
}

private func calculateSizeOfDirectory(directoryPath:String, contents:[String]) -> UInt64 {
    var size : UInt64 = 0
    for pathComponent in contents {
        var error : NSError?
        let fileManager = NSFileManager.defaultManager()
        let path = directoryPath.stringByAppendingPathComponent(pathComponent)
        if let attributes : NSDictionary = fileManager.attributesOfItemAtPath(path, error: &error) {
            size += attributes.fileSize()
        } else {
            NSLog("Failed to read file size of \(path) with error \(error)")
        }
    }
    return size
}
Indisputable answered 25/8, 2014 at 11:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.