Manjusaka

Manjusaka

Guide to Function Parameter Naming Conventions in Swift 3

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, this process is quite tedious. Due to the significant changes in Swift 3 compared to the previous version, I have to admit that there are no shortcuts other than spending more time. However, this experience has brought some benefits: I have 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. What's more painful is that the entire migration process is not as simple as modifying the code. You also need to patiently adapt to the new changes in Swift 3. In a sense, modifying the code is 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 the first step of your long journey.

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

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 can be omitted when calling the function, following the good ol' Objective-C conventions. For example, we can write code like this:

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

In Swift 3, there is also a way 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, you need to explicitly tell the compiler about this:

    // 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: tell the compiler that we don't need an external label for the first parameter when calling the function. This way, we can call the function in the same way as in Swift 2.

In addition, 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 may be the first problem you encounter.

Now the code can be compiled and run, but you must know that you need to read the Swift 3 API design guidelines repeatedly.

☝️ A little life experience: you need to recite the Swift 3 API design guidelines repeatedly, which 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 like this, 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 read the Swift 3 API design guidelines repeatedly until you can recite them.

Make sure 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 know that the parameter belongs to the type NSError).

More Issues#

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

What we have discussed above 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, according to the knowledge you have, you may do this:

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

To be honest, this code looks unreadable. 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 doesn't solve the problem I mentioned earlier.

When we call this function, how can we intuitively know that we need to pass in a Web URL as the argument? What you can know in advance is that you need to pass in a variable of type String, but you don't know that you need to pass in a Web URL.

Similarly, in a large project, we need to clearly understand the purpose of each parameter. However, it is obvious that we have not solved this big problem yet, for example:

  • 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...

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

Back to the code, we can add corresponding labels to the parameters to solve this problem. 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

To be honest, you can close the browser here, but in fact, the following part is the most valuable.

Now, let's take a look at the wording issue when naming function parameters:

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

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

Little Tricks You May Not 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 achieve a perfect unity, because you give 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, for example:

    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 indiscriminately, you may pay a painful price.

Conclusion#

Many times, when using Swift 3, you will encounter many 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.