radex.io

archiveaboutbookstwitterrss

Swifty methods

If you prefer watching to reading, I also gave a talk on the same topic.

Most programmers learning and writing Swift now are people with existing experience in Objective-C. Many of them have worked with this language, writing iOS and Mac apps, for years. If you’re reading this, there’s a good chance this also describes you. It’s very tempting for us to write Swift the way we’d write Objective-C. It’s all too easy to cram existing habits, conventions and ways of thinking into Swift. But, of course, Swift is a very different language from its predecessor and so it requires new approaches.

Now, there has been a lot of great articles touching on Swift’s cool features like structs, enums, tuples and generics; some people even tried to write functional code in it, but today I want to talk about something more subtle and nuanced: methods.

Even something as trivial as method naming has been a great source of disagreement in the Objective-C community. The language is known for long, verbose names like performSelectorOnMainThread:withObject:waitUntilDone: or tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:. Some people love it, some hate it. There have been good arguments on both sides, but it’s all a moot point — the truth is, that’s the Objective-C convention. And conventions are a good thing — it’s easier to read each other’s code and work together if we all decide on a common coding style.

But Swift, as a brand new language, doesn’t have established conventions yet, so the discussion is open again — and naming aside, there are many great features related to how we define methods that let us write cleaner code than in Objective-C.

So let’s explore the topic.

Verboseness is not clarity

The usual justification for Objective-C’s style of long method names is that it makes them clear and descriptive. They say it makes the code self-documenting and self-explanatory.

To some extent, that’s certainly true. There’s definitely a lot of value in having things say what they do. I don’t think anyone questions that. But very often I found that Objective-C’s methods are verbose, not necessarily clear. The reality is, most code is too complicated to be accurately explained in a few words anyway. At the same time, there’s a lot of methods that are more verbose than necessary to make them clear.

Here’s an example.

[string componentsSeparatedByString:@"\n"];

It’s fairly verbose, but is it truly clear? Now, I’m pretty sure you already know what this method does, but if you didn’t — could you figure out what it does? It surely has something to do with separation, it takes a string and it spits out an array… And you probably have some context around this method, so you’ll be able to make a good guess. But what the hell “components” mean? Maybe it means something not obvious at the first glance? If I saw this method for the first time ever, I’d be 95% sure what it means, but I wouldn’t be completely sure until I took a look at the documentation.

Let’s compare it to a piece of Ruby:

string.split("\n")

It’s shorter and less verbose, yet I would argue it’s just as clear. It’s probably obvious from the context that it takes a string and returns an array, and it has something to do with splitting. Again, we can only be 95% sure that it does what we think it does until we check — but adding more words doesn’t help.

We have the tools

As I hope I’ve shown, no matter how verbose you make a name, it never tells the whole story. It helps, sure, and it’s good when it’s clear, but it’s always just a label, not documentation.

So let’s say a young Swift developer sees map, filter and reduce for the first time in her life. Are they self-explanatory? Heck no. They’re clear enough once you understand them, but the first time you see those methods, they’re foreign.

But here’s all you have to do: ⌥click the method name.

Xcode inline documentation

There’s no step two. It’s that simple: you don’t know what something means, you ⌥click on it, you read the sentence or two of explanation and now you know. And once you know, map is just as good as arrayByEnumeratingObjectsUsingBlock.

Noise

Some people say that “clarity trumps brevity”. And they’re absolutely right. Except for the fact that brevity can be a factor in clarity. Verboseness doesn’t come for free. Yes, modern tools like code autocompletion help a lot in writing long names, but you still have to read them. It takes some real effort to mentally parse and understand the code you’re reading. It’s easier and faster to read code that’s clear than code that’s confusing, obscure or too clever. But it’s also easier to read short, to-the-point code than one that’s overly verbose.

The trick in finding the sweet spot is to maximize the things that explain what the code does and remove as much noise as possible — the things that don’t really add information.

Here’s a quick example, something as trivial as declaring a date object:

NSDate *date = [NSDate date];

Why do we have to say “date” so many times? Why is it necessary to specify the type of date variable when it’s obvious from the context? And why are semicolons needed?

Compare with Swift:

let date = NSDate()

Obviously it’s a silly-simple example, but Swift wins here. In both cases, the code is clear in what it does, but in the latter, all the noise is gone. We only say the things that actually matter, and everything else is stripped. That’s a very good thing. There’s less code that we have to understand — and we don’t have to fish out meaning from all the noise.

Another example, this time about method names:

[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(foo:) userInfo:nil repeats:NO]

Let’s focus on the scheduledTimerWithTimeInterval part for a second. Why are we repeating “timer”? We just said “NSTimer”, what else could we be scheduling? And why say “time interval” if “interval” is just as clear (again, we’re scheduling a timer, so that part is obvious). How about:

NSTimer.schedule(interval: 1.0, ...)

Notice that I also removed the word “with” and instead moved “interval” to an argument label. Objective-C often tries to make the method calls sound like real sentences, but the truth is that programming is simply not like natural languages. (If you go too far down that path, you end up with AppleScript. And no one likes AppleScript.)

Finally, let me quote Chris Lattner, the guy who designed Swift (emphasis mine):

The purpose of ? in optional chaining is to bring clarity to the code. Many people misunderstand this about Swift: the goal is not to achieve terseness of code, it is to achieve clarity of code by removing boilerplate and unneccesary adornments.

It’s up to you to interpret these words, but the way I see it, I’m not trying to achieve brevity at the expense of clarity, I want to increase clarity by removing noise.

There’s a spectrum

Code is not made equal. It would be a mistake to apply the same naming convention to everything. Some methods are generic and broadly used. Some are rare or domain-specific.

Consider these functions and methods: map, reduce, stride, splice. These are very short and easy to remember. They’re not exactly self-explanatory, but that’s okay, because they are standard library methods that you’re going to use many, many times so the benefit of a short name is greater than the price of having to learn it first.

On the other side of the spectrum, there’s your application code. You probably don’t want to give methods in your view controllers obscure single-word names. After all, they’re probably going to get called once or twice in your entire codebase, they’re very specific for the job, and once you start working on another class, you’ll totally forget what the thing was.

I hope you see where I’m going with this. If you’re writing library/framework-like code, or some base class that’s going to be used throughout the app, it’s okay to use short and sweet names. But the more application-specific it is, the more you want clearer, more descriptive, intention-revealing methods.

To label or not to label

I think parameter labels are a good thing — it’s much easier to understand a function call when the meaning of its arguments is explicitly stated by their labels, and not merely determined by their position.

You could make a reasonable argument that it’s usually obvious from the context anyway, and when writing, it doesn’t matter in a language like Swift because we have auto-completion. Well, maybe, but unlike what I described in sections above, labels are part of Swift style because the language itself pushes you to use them. You have to do extra work to opt out of argument labels

But I think there are some legitimate cases where it makes sense to do so. Here’s an example:

CGPoint(x: 10, y: 10)
CGRect(x: 5, y: 5, width: 100, height: 50)

I think the labels are redundant and quite annoying here. We use geometry primitives all the time. And the order of arguments never changes. x always comes before y, and width always comes before height. It would be totally fine if the geometry initializers were defined without labels, like so:

CGPoint(10, 10)
CGRect(5, 5, 100, 50)

It’s still quite obvious what is what, but I don’t have to mentally jump between labels to get to the numbers, which are what actually matters.

On the flip side, there are cases where it makes sense to use labels even when the default behavior is to have none.

Take stride for example:

stride(from: 1, through: 100, by: 2)

This would be totally incomprehensible without documentation if not for the labels.

Optional parameters

Here’s how you make a simple window object on the Mac:

[[NSWindow alloc] initWithContentRect:frame styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO screen:nil]

Here’s the problem I have: the only interesting thing in the entire invocation is the content frame. Everything else is left standard — which makes it noise.

Swift improves the situation dramatically, because you can set default values to parameters. So you could define the initializer like so:

init(contentRect: NSRect,
     styleMask: NSWindowMask = .Titled,
     backing: NSBackingStoreType = .Buffered,
     defer: Bool = false,
     screen: NSScreen? = nil)

Which reduces the initialization to short:

NSWindow(contentRect: frame)

… and you only have to write any of the others parameters if you actually need non-standard behavior.

One nice thing about labeled arguments is that if you have a bunch of optional parameters and you want to put a non-default value in the fourth one, you don’t have to type out all the arguments before it. In fact, the order of parameters with default values doesn’t matter in Swift at all.

Final thoughts

You don’t have to agree with everything I’ve said here. That’s not what I’m trying to achieve. But what I want is that you keep an open mind about the way you write Swift. Explore the language. Play with different techniques and styles. Make it fun. Heck, if you have enough time, try some other languages like Ruby and Haskell — just to see how they get things done. And don’t listen to the experts, because there are none. That, of course, also includes me.

But try doing things differently. Remember that there is no convention, no official Swift coding style yet. You, my dear reader, are going to be one of the people who are going to define what the convention is. So tear down the assumptions and make your own.

Published September 14, 2014. Send feedback.