With the introduction of Swift, incredibly long Objective-C APIs now have the chance to be updated to shorter, more readable methods. However, reducing the number of characters to type does not necessarily increase clarity. Radek Pietruszewski shares how he removes the noise from code to find just the right balance between clarity and brevity.
Introduction (0:00)
“Programs must be written for people to read, and only incidentally for machines to execute.”
- Structure and Interpretation of Computer Programs
Now, you might be thinking, ‘that’s an exaggerated quote’. After all, apps are rather more than just incidentally for computers to run. Underlying that quote is a very important idea that I want to get across, and is something that I think other programmers don’t fully appreciate. The idea is that code we write shouldn’t be comprehensible just for the compiler. It should be easy to understand for the human reader, for programmers - people like us.
Clarity: Clever Is Dumb (1:32)
I believe we should write code with clarity in mind. We should write code or, at least, we should try to write code in such a way that, not unlike good design, it feels almost inevitable and obvious. Of course, writing obvious code is not an obvious task. It takes time, editing skill, and good taste.
Other programmers have a natural tendency towards writing clever code. Clever code is something that makes you feel good. It makes you feel smart, and about six months down the line, even you don’t remember what it meant. So, clever code is difficult to understand. It’s not clear, it’s dumb.
Clarity Is Worth It (2:03)
In the long-term, clarity is worth it. Clarity, or writing clear code, makes you a better programmer and makes for better programs. It takes a while longer upfront to write it, but what you end up with is a codebase that’s much easier to work with. You get code that takes a lot less effort to read and understand. And, I think you can avoid making some bugs, because how can you screw up something that feels obvious? It’s much easier for things to go wrong when what you work with feels complex and difficult to understand. If nothing else, I hope we can all agree on this point: clarity is important, and clarity is worth it.
Clarity != Verbosity (3:08)
In my opinion, Objective-C has a mistaken idea about what clarity is. I think that Objective-C often confuses clarity with verbosity. The place where it’s most obvious is names. Of course, Objective-C names things like this:
stringByReplacingOccurrencesOfString:withString;
[string componentsSeparatedByString:@"\n"];
It’s beautiful. It reads like poetry. Some of it doesn’t even fit on the slide. Now, what’s wrong with this? I have an example. I’m sure all of you already know what this method does, but if you didn’t, could you figure it out? I’m sure you could. There is a name and there is some context around it, so it’s pretty clear. But if you didn’t already know, what is that? What are components? Does it mean something special? Is there an NSComponent
?
It’s pretty clear, but the truth is that even something as simple as this is still complex enough that it’s impossible to fully and accurately explain in just a few words. What we have here is, say, 95% clarity and 100% verbosity. Is there an alternative? Here is some Ruby:
string.split("\n")
What does it do? Well, it splits a string with a new line. I don’t know about you, but it’s pretty clear to me. It’s not really clearer than Objective-C, but adding more words just doesn’t really help. What if that’s not enough, though? What if you don’t know what the components mean? What if you see some method for the first time in your life and you don’t know what it does?
How do you find out? You alt-click on it, right? We have the tools, and we have Xcode. When you don’t know what something means, you alt-click on it, read the description, and now you know. Once you know, split
is just as clear as componentsSeparatedByString
.
Now, if you have been writing in Objective-C for a long time, you probably want to stop me right there. You may be thinking, ”Well, why not name this componentsSeparatedByString
?” It is somewhat clearer, and besides, what is the point? What am I trying to achieve here? Why am I trying to save a bunch of characters? Clarity trumps brevity, right? Yes, yes it does.
Verbosity Isn’t Free (5:40)
What I think some people don’t appreciate is that brevity is a factor in clarity. Verbosity doesn’t come for free. It’s true that we have modern tools like autocompletion that help us in writing those long method names, but we still have to read them. It took some real effort to mentally parse and understand the specific source code. While it’s true that clear code is obviously better than confusing code, I think it’s also true that reading short code is easier and faster than reading verbose code.
What you need to do is find the sweet spot between brevity and verbosity. That’s where clarity lies. The way to achieve that is to remove all of the noise, the things that don’t actually add any information. I’m saying that split
is just as clear as componentsSeparatedByString
, and that something like replace
is just as clear as stringByReplacingOccurrencesOfString:withString:
.
I actually think that replace
is clearer than the latter because it lacks all of the noise. stringByReplacingOccurrencesOfString:withString
uses string
three times. What’s the point? We are trying to encode type information in the method name. That’s nonsense. As far as I’m concerned, as a programmer, I don’t think it’s really helpful, either. In 99% of cases, it’s obvious. If it isn’t, I can just alt-click on it. The words “by”, “of”, and “with” are just pure noise, too. They carry exactly zero information. Why not just replace
? It’s one simple verb, and still pretty clear. You could figure it out.
Remove the Noise (7:31)
Let’s talk about Swift. I think that this different philosophy for naming things fits perfectly within Swift. The idea of removing noise is all over the language, like with type inference. Why be explicit about types in method bodies? Why repeat ourselves, when it’s obvious? It is just noise, so burn it.
Why type semicolons, pointer asterisks, or unnecessary parentheses? It’s not a lot, but it’s noise, so burn it. Swift has all sorts of shortcut forms for closures. Like any normal language, it has operators and features like optional chaining.
Less Code to Understand Is a Good Thing (8:38)
Less code to understand is a good thing. We don’t have to fish a meaning from all of the noise. Here’s another example of noise. This is how you make a simple Window object on the Mac.
[[NSWindow alloc]
initWithContentRect: frame
styleMask: NSTitledWindowMask
backing: NSBackingStoreBuffered
defer: NO
screen: nil]
Here is what keeps bothering me all the time: this initWithContentRect:frame
part is actually the only thing that’s interesting here. Everything else is standard. I haven’t changed this. It’s default, which makes it noise. Swift comes to the rescue because finally we can define arguments with the full values, so you could define the initializer like so:
init(
contentRect: NSRect,
styleMask: NSWindowMask = .Titled,
backing: NSBackingStoreType = .Buffered,
defer: Bool = false,
screen: NSScreen? = nil)
That would bring this entire monstrous implication of a simple thing to just NSWindow(contentRect: frame)
, which, I think, is a bit better.
Swifty APIs (9:27)
Now I want to take this idea of balancing brevity with verbosity, and increasing clarity by removing noise one step further. Let’s talk about this in the context not just of how we name things, but of how we approach designing APIs. Here’s one example:
[NSTimer scheduledTimerWithTimeInterval: 1.0
target:self selector: @selector(foo:)
userInfo: nil repeats: YES]
- (void) foo: (NSTimer *timer) {
NSLog(@"Hello world!")
}
This is how you schedule a simple timer in Objective-C. Every second, I’m printing “Hello world!” to the console. Let’s try to remove some noise here. I’ll go over the method name word-by-word. “Scheduled” is important because we’re scheduling the timer. We just said NSTimer
, so we’re repeating ourselves with the word “Timer”. “With” is pure noise; it has zero new information. ”Time” is a waste, because obviously, what other kind of interval would we want to schedule here? Now it’s a bit better, but there’s still a lot of work to do.
NSTimer.schedule(interval: 1.0,
target: self,
selector: "foo:",
userInfo: nil,
repeats: true)
func foo(timer: NSTimer) {
println("Hello world")
}
How can we improve this? What about the target selector thing? Why do we have to define a new method to create a simple timer? Sometimes it makes sense, but not here. In my experience, most of the time you could just do with a simple block.
NSTimer.schedule(interval: 1.0,
userInfo: nil,
repeats: true) {
println("Hello world")
}
What else? We have this userInfo
thing. Without a target selector, it’s quite useless, so let’s remove that. Now that’s an improvement.
NSTimer.schedule(interval: 1.0, repeats: true) {
println("Hello world")
}
We still have this repeats: true
. It’s fine, so you could leave it, but let’s try doing something different. Let’s try to make this API a little bit more delightful by encoding this information in the method name itself:
NSTimer.schedule(every: 1.0) {
println("Hello world")
}
We can use “every” to indicate every second. This kind of makes sense. But you know, removing noise is only part of the equation. Remember, the ultimate goal here is to increase clarity. As far as clarity is concerned, “1.0” kind of bugs me. What’s the meaning? Here’s an idea I stole from Ruby on Rails: how about we add methods on numbers like second, seconds, minute, and hours. That returns NSTimeInterval
, and now we can write something like this.
NSTimer.schedule(every: 1.second) {
println("Hello world")
}
Want a non-repeating timer? Replace every
with after
, and schedule it for one second. It’s quite cute. I would say that it’s clear and readable. You be the judge, but if you ask me, we didn’t sacrifice clarity to achieve brevity. Instead, we increased clarity by removing all of the noise.
Recap (12:40)
As a quick recap, here are the four ideas I’m trying to sell you today:
1: Focus on clarity
Make it one of your priorities. Clear code is easier to work with, it’s easier to read, and it’s easier to understand. You can avoid making bugs, sometimes. Clarity is an important factor of quality, both of the program and the programmer.
2: Don’t write clever code
It serves no one. It’s difficult to understand, and it’s not clear. Try writing obvious, simple, and clear code instead. It’s not easy, but it’s worth it.
3: Clarity is not achieved by being verbose
Clarity is achieved by maximizing the signal to noise ratio. Brevity is neither the point, nor the goal. But, it is a factor in clarity, because reading and understanding takes effort. Clarity is not verbosity.
4: Keep an open mind about the way you write Swift
Don’t just copy what you were doing in Objective-C because Swift is a different language. It affords, and I would say requires, a different approach, so think different. Thank you.
Q&A (14:24)
Q: Do you have an example of abusing brevity, or an example of an API that is too smart in Swift, and therefore not really readable?
Radek: Do you mean something that is too much on the brevity side and it is not clear? I’m not sure, I certainly see those kind of things. I saw this crazy thing with auto layout by operators that made it look like the visual format language. It’s bad for many reasons, maybe only because of the brevity. It probably isn’t a good thing to focus so much on brevity that someone who has not seen it before does not understand the code. The point is that there is a balance you have to strike, and different people have a different opinion of where the sweet spot is.
Q: You mentioned a hypothetical alternative API to NSTimer
. Do you think that Apple will do something like this? If not, do you think it is worth it for developers to wrap those APIs to make them feel more comfortable in Swift?
Radek: I think so, eventually. NSTimer
is kind of an extreme example because you could improve it a lot in Objective-C. You could use blocks, and they didn’t. It is a very old API. In the newest Xcode betas, we have seen more and more Foundation APIs. With time we’ll see basic things, like NSTimer
, be replaced. Maybe even Cocoa, but that is long term.
I’m not sure if it is really a great thing for everyone to try to build on top of and replace all of the APIs for themselves. That seems kind of bloated, but you can if you want to. NSTimer
is just an example from the Standard Library because everyone knows it and can apply it to their code. The examples are definitely better suited for the Standard Library, because when you are working with application code, you are definitely going to be more verbose. It is not very generic, it is more domain specific.
You can read more about Radek’s proposals on his blog.
Receive news and updates from Realm straight to your inbox