Manjusaka

Manjusaka

A guide to function parameter naming conventions in Swift 3.

Title: Function Parameter Naming Guidelines in Swift 3
Type: Tags
Date: 2016-10-09 22:13:04
Tags: [iOS, programming, Swift]
Categories: [programming, translation]
Table of Contents: true

Yesterday, I started migrating this Jayme project to Swift 3. This is my first time migrating a project from Swift 2.2 to Swift 3. To be honest, the process was quite tedious. Due to the significant changes in Swift 3 compared to the previous version, I had to admit that there were no shortcuts other than spending more time on it. However, this experience brought some benefits: I gained a deeper understanding of Swift 3, which is probably the best news for me. 😃

During the code migration process, I had to make many choices. It was not just about modifying the code; I also had to patiently adapt to the new changes in Swift 3. In a way, modifying the code was just the beginning of the entire migration process.

If you have decided to migrate your code to Swift 3, I recommend you to read this article as your first step on this long journey.

If everything goes well, I will write a blog post in the near future to document the details of the entire migration process, including the decisions I made. But for now, I will focus on a very important issue: how to correctly write function signatures.

Introduction#

First, let's take a look at the differences in function naming between Swift 3 and Swift 2.

In Swift 2, the label of the first parameter in a function could be omitted when calling the function, following the good ol' Objective-C conventions. For example, we could write code like this:

    // Swift 2
    func handleError(error: NSError) { }
    let error = NSError()
    handleError(error) // Looks like Objective-C

In Swift 3, it is also possible to omit the label of the first parameter when calling a function, but it is not the default behavior:

    // Swift 3
    func handleError(error: NSError) { }
    let error = NSError()
    handleError(error)  // Does not compile!
    // ⛔ Missing argument label 'error:' in call

When encountering this situation, our first reaction might be like this:

    // Swift 3
    func handleError(error: NSError) { }
    let error = NSError()
    handleError(error: error)    
    // Had to write 'error' three times in a row!
    // My eyes already hurt 🙈

Of course, if you do this, you will quickly realize how cumbersome your code becomes.

As mentioned earlier, in Swift 3, we can omit the label of the first parameter when calling a function, but remember that you need to explicitly tell the compiler about it:

    // Swift 3
    func handleError(_ error: NSError) { }
    // 🖐 Notice the underscore!
    let error = NSError()
    handleError(error)  // Same as in Swift 2

You may encounter this situation when using Xcode's built-in migration tool.

Note that the underscore in the function signature means that we are telling the compiler that the first parameter does not need an external label when calling the function. This way, we can call the function in the same way as in Swift 2.

Furthermore, you need to be aware that Swift 3 changed the way functions are written to ensure consistency and readability: we no longer need to treat different parameters differently. I think this might be the first issue you encounter.

Now that the code can compile and run, you must know that you need to constantly refer to the Swift 3 API design guidelines.

☝️ A little life experience: you need to recite the Swift 3 API design guidelines at any time, as it will unlock the new perspective of Swift development for you.

Step 2: Prune Your Code#

Pruning

Let's take another look at the previous code:

To prune our code, you can trim it by removing redundant type names, such as removing the type information from the function name.

    // Swift 3
    func handle(_ error: NSError) { /* ... */ }
    let error = NSError()
    handle(error)   // Type name has been pruned
    // from function name, since it was redundant

If you want to make your code shorter, more concise, and clearer, I tell you, as a certified developer, you must recite the Swift 3 API design guidelines until you can write them by heart.

Make sure that the process of calling a function is clear and explicit. We determine the naming and parameters of a function based on the following two points:

  • We know the return type of the function.
  • We know the type of the parameter (for example, in the example above, we undoubtedly know that the parameter belongs to the type NSError).

More Issues#

Now, please pay close attention to what we are going to discuss next. ⚠️

What we have discussed so far does not cover all possible situations. In other words, you may encounter a special case where the type of a parameter cannot intuitively reflect its purpose.

Let's consider the following situation:

    // Swift 2
    func requestForPath(path: String) -> URLRequest {  }
    let request = requestForPath("local:80/users")

If you want to migrate this code to Swift 3, based on the knowledge we have so far, you might do this:

    // Swift 3
    func request(_ path: String) -> URLRequest {  }
    let request = request("local:80/users")

Honestly, this code looks poorly readable. Let's modify it a bit:

    // Swift 3
    func request(for path: String) -> URLRequest {  }
    let request = request(for: "local:80/users")

OK, now it looks much better, but it still doesn't solve the problem I mentioned earlier.

When we call this function, how can we intuitively know that we need to pass a Web URL as the argument? All you can know in advance is that you need to pass a variable of type String, but you are not clear about passing a Web URL.

Similarly, in a large project, we need to clearly understand the purpose of each parameter. However, it is clear that we have not yet solved this major problem, such as:

  • How do you know that a variable of type String represents a Web URL?
  • How do you know that a variable of type Int represents an HTTP status code?
  • How do you know that a variable of type [String: String] represents HTTP headers?
  • And so on...

⚠️ Therefore, I will give you a little life experience: be cautious when pruning your code

Going back to the code, we can solve this problem by adding corresponding labels to the parameters. Take a look at the following code:

    func request(forPath path: String) -> URLRequest {  }
    let request = request(forPath: "local:80/users")

Now, the code looks clearer and has better readability. 🎉 Congratulations~

Hooray

Honestly, you can close the browser here, but in fact, the most essential part is yet to come.

Now, let's take a look at the wording issue in function parameter naming:

    func request(forPath path: String) -> URLRequest {  }
    // The word 'path' appears twice

This code looks good, but if you want to make it even better, please continue reading.

The Little Trick You Don't Know#

This little trick is simple: reflect the type and purpose of the parameter in the context, so that you can prune your code without thinking.

Prune with no mercy

Well, let's take a look at the following code.

    typealias Path = String      // To the rescue!

    func request(for path: Path) -> URLRequest {  }
    let request = request(for: "local:80/users")

In this example, the type and purpose of the parameter are perfectly unified because you have given String an alias called Path in the context.

Now, your function still looks concise, readable, and non-repetitive.

Similarly, you can write beautiful code using the same approach, such as:

    typealias Path = String
    typealias StatusCode = Int
    typealias HTTPHeader = [String: String]
    // etc...

As you can see, you can write concise and beautiful code to your heart's content.

However, please remember that going to extremes will change the taste: this little trick will add extra burden to your code, especially when your code has multiple levels of nesting. Therefore, please remember that if you use this trick blindly, you may pay a painful price.

Conclusion#

Many times, when using Swift 3, you will encounter difficulties in naming functions.

Accumulating some code snippets may help you a lot:

    func remove(at position: Index) -> Element {  }
    employees.remove(at: x)

    func remove(_ member: Element) -> Element?  {  }
    allViews.remove(cancelButton)

    func url(forPath path: String) -> URL {  }
    let url = url(forPath: "local:80/users")

    typealias Path = String // Alternative
    func url(for path: Path) -> URL {  }
    let url = url(for: "local:80/users")

    func entity(from dictionary: [String: Any]) -> Entity { /* ... */ }
    let entity = entity(from: ["id": "1", "name": "John"])
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.