How to Avoid Protocol Orientation Obsessed Programming
About a year ago, I was catching up on the WWDC videos from 2015 when a particular conference talk caught my attention. It seems to have caught everyone’s attention. Given the Swift-related content created over the past year, scores of people have leaped toward a radically new programming paradigm.
I am, of course, referring to the mind-blowing talk on Protocol Oriented Programming presented by David Abrahams. If you haven’t seen it yet, and you write apps in Swift, it is absolutely worth checking out! Seriously, do it now.
What follows is not an attempt to diminsh the coolness of what was presented during that talk. Protocol extensions, when applied correctly, are a wonderful tool with many practical uses that we’ll go over at the end of this article.
The problem is something we are all guilty of (myself definitely included). When you have a shiny new hammer, everything is a nail.
So for the last year and a half, protocols have been that hammer, and our projects the nail. What did we learn?
The genesis of POOP
It always starts out as an innocent desire to learn something new. It’s not all too different from unpacking that Nintendo 64 on Christmas and staying up all night playing Goldeneye with your siblings.
Yes I am aware of how much that dates me.
Anyway, you figure nothing is stopping you from refactoring the entire application into these reusable protocols, so you go to town.
“Wow, you really can move everything from a class to a protocol,” you think to yourself. “Do classes even have a purpose anymore? Should we replace everything with structs?”
Drunk with power, your head spins out of control. Your classes and structs no longer have any visible methods in them. They’re just a list of protocol conformances. You have assured yourself that this is the way the cool kids are doing it.
Protocol Orientation Obsessed Programming is the product of the desire to over-apply protocol extensions, especially in situations where they just aren’t needed.
POOP is a perfectly understandable reaction to an awesome new tool, but just as I have said before, there are always tradeoffs with anything you do in programming. It’s never enough to simply look at the benefits. We must always consider the costs.
So what are the tradeoffs with Protocol Oriented Programming? Why might we want to avoid the impulse to refactor everything to protocols and protocol extensions? What makes POOP poop?
It all comes down to Code Readability.
How Protocol Orientation Obsessed Programming harms the readability of your code.
When you work with a team of developers, the number one contributor to your success is effective communication. Readable code leads to better communication. Unreadable code slows the team down because it takes more time for everyone to figure out what it does.
As a developer on a team, you should always strive to reduce the cognitive burden your code places on your colleagues.
Having readable code means more than simply picking the right class and function names. It has a lot to do with presenting the appropriate amount of information in a single place.
Another developer ought to have no problem figuring out what your code does just by looking at one file.
There’s a term for this. We call it local reasoning.
It means people should be able to reason about your code without having to move from place to place too much. It is a design philosophy that favors upfront explicit code.
When you write code with the intent to preserve local reasoning, you try to avoid complicated class hierarchies and designs that force other developers to jump from file to file just to get a mental picture of what your code does.
Protocol Orientation Obsessed Programming is problematic because it makes local reasoning damn near impossible.
To better understand why that’s the case, I would invite you to please
The Amazing Exploding Struct
Let’s say you have a basic Milkshake struct. It has some variables and functions you may be familiar with.
Now we may not necessarily agree with the functions granted to our Milkshake struct, but we can agree that we have a readable chunk of code here.
That is to say it’s all spelled out for us. We don’t need to look elsewhere to get a sense of what this struct does. It is easy to reason about.
What would happen if we were to refactor parts of our Milkshake to some separate protocols?
Well, it’s not terrible. Technically, it still works just like our other struct definition.
Unfortunately, it’s much less readable.
I understand that an experienced programmer ought to have no problem deducing that my Milkshake struct acquires its ability to transport boys to a yard via protocol conformance.
However, this conclusion isn’t immediately apparent. The person reading the code has to expend some mental energy to arrive at that understanding.
In a controlled setting like this silly little example, it’s fine. But when you are working on a big project with multiple team members, you impose that cost on everyone who has to interact with your code. It slows the project down.
Forcing people to think, when there is no apparent benefit to doing so, does little more than waste everyone’s time. Code like this burns barrels of cash to the ground.
It gets much worse when you start shipping the protocols and extensions off to separate files. Unless you command click on each of those protocols, you will have no idea what that struct does. It kills local reasoning.
Come to think of it, the term Protocol Oriented Obfuscation might be more appropriate given its tendency to make code less direct.
The difference between POP and POOP.
Hopefully I haven’t swung your opinion the other way around. I do not intend to say we should throw up our hands and never use protocol extensions again. I like my new toy! It does amazing things.
I am merely making a case for some moderation. Let’s not be too hasty with the application of protocol extensions. Because there is a readability tradeoff, we should be picky about when and where we use them.
POP becomes POOP when we stop thinking about the underlying reasons why a protocol can help us. POOP is the blind application of protocols to all situations under the misguided notion that protocols are always superior to boring old structs and classes.
Hopefully I have convinced you otherwise. Now let’s try to understand some of the benefits of protocol extensions.
What do we gain with protocol extensions?
So why did we go about all this business of making Swift a protocol oriented language? In a practical sense, what are we trying to accomplish by doing that?
In a word, Composability
Protocol extensions give us a way to share functions and variables between classes and structs without using inheritance. So long as two separate entities share the same required properties, both get the new behavior. That’s cool!
Before we had protocol extensions, we had to create a separate object to hold onto the functionality we wanted to share between them. We called it Object Composition.
Lots of developers still use object composition to this day. You are almost certainly using it in your work.
Remember that time you made a reusable view that you put in a bunch of different screens of your app? That’s object composition.
You didn’t force your view controllers to inherit from some superclass that contains the behavior. You composed your view controller out of reusable parts, one of which was your custom view.
With Protocol extensions, we gain an extra way to do composition. Now new behaviors can be added via protocol conformance in additon to object composition.
It’s an extra tool in our belt, and when used correctly, it can make us more productive.
Some practical uses for protocol extensions
Given that the primary benefit of using protocol extensions is composability, most of the practical uses amount to reusing behaviors in similar entities. If you find yourself needing the same behavior in more than a few places, you might want to consider using a protocol extension to avoid the duplication.
Reason #1. Multiple entities use the same behavior
Let’s go back to our example. We showed how we can factor out the “SipBySipConsumable” behavior by moving it to a protocol extension. Of course, it seemed kind of silly to do that since there are no other entities that use the behavior.
But what if we were programming some kind of game where the purpose is to drink all kinds of different beverages? Would it be so silly then?
Nope. Not at all.
By moving the code to a protocol extension, we avoid the need to duplicate the same behavior over and over in all other beverage-like entities.
Remember, we must always consider the cost to benefit ratio. Fifteen duplications of the same function is much harder to maintain than a single implementation.
Thus when lots of things need the same behavior, we can accept the slight ding to readability.
POOP results from having no good reason to move the code to a protocol extension. POP is the judicious application of protocol extensions to make the codebase more maintainable.
Reason #2. We really really need to unit test this thing
With protocol extensions, we gain the ability to extract behaviors from the things that implement those behaviors. That means we can test the behavior in an isolated environment where none of the dependencies of the class or struct it is bound to apply.
This is a huge boon to unit testing. It cannot be overstated.
It means our unit test suite can use simpler entities than those used in our production code. It effectively makes unit testing much easier. If you’re curious about how that works, have a look at my Easy Mocking With Swift Protocols article.
On a related note, protocol extensions shouldn’t be an excuse to TDD your codebase to absolute ruin. Unit tests have their own tradeoffs, which is another topic I discuss in a separate article What Should We Unit Test In Our iOS Apps?
In any case, when it becomes very apparent that you need to unit test something, protocol extensions are a powerful tool for doing that.
Reason #3. Reusing the same code between iOS and MacOS
Protocol extensions, when combined with associated types, can tear down some big barriers when it comes to sharing code between different operating systems.
Let’s say you have a UIView subclass with some behavior that you want to port over to the MacOS version of your app. Unfortunately, Cocoa just isn’t the same as UIKit. It uses NSViews, not UIViews. Subclassing is out of the question.
But with protocols and associated types, you can define a behavior that both an NSView and UIView subclass can share. It makes cross-platform codebases much more maintainable!
Sorry I haven’t written an article on that yet. I’ll get on that. When I do, I will reference it here.
Let your mind be blown, but keep your : in tact.
We live in an amazing time with fantastic new tools coming out each year. We have every reason to be excited, but we also have lots of reasons to temper some of that excitment.
Protocol Oriented Programming and Swift are paving the way toward a new era of programming. It can be a multiplier on our productivity, or it can be a noose with which we hang ourselves.
As long as we prioritize code readability above all else, we’ll make some apps that POP and avoid stepping in a big pile of POOP.
I hope you had as much fun reading this as I had writing it. If you enjoyed this story, agree with it, disagree with it and wish to silently stalk me until all of your premeditated actions culiminate in a moment of murderous rage, or you simply found it useful, please recommend and follow!
Support Practical iOS Development
Did you like what you see? Did I save you some time?
If so, please take a moment and support me on Patreon.
As a supporting member, you will get exclusive access to content, a vote on the topics covered, and at the higher levels, a “help me get unstuck” pass where I will personally help you fix a problem with your iOS app.