It's Time To Fix That Swift Class
Recently I faced an interesting problem. A new client wanted to present some HTML content in a UIWebView — sometimes from an external URL, sometimes from a local URL.
The client’s application was already presenting HTML, so I thought it’d be easy to reuse that component. To my surprise, the WebViewController class was 817 lines long. Presentation, flow, error handling, business logic, and more. It was a nest of conditionals with no tests.
I noticed a fileUrlString
property, so I decided to create an instance and see what happened.
Nothing appeared on the screen. I forgot to set the boolean property, loadAutomatically
, to true.
So I made the change, and it finally loaded, but it didn’t show the loading spinner. I forgot to set the boolean property, showLoadingSpinner
, to true.
I made the change and still it didn't work.
I tried to debug that code for about 10 minutes and found something interesting: the showLoadingSpinner
was only used for external URLs.
However, the spinner for local URLs was a new requirement for the specific feature I was working on. It was a rather heavy file and it could take a while to load, especially on older devices.
At the same time, it was also mandatory that the other sections with local URLs didn't show a loading spinner, since they wanted it to have a native look and feel.
A senior engineer stepped in and told me to add a boolean flag, showFileLoadingSpinner
.
“Doesn't it have enough logic already?” I couldn't help but ask. “And there are no tests!”
“The QA team will test the app for regressions,” he assured me.
“What if there’s a regression?” I asked.
“Then they’ll reject the pull request and you’ll have to fix it.”
“How long does that interaction normally take?” I wondered aloud.
“Well, if you're lucky, two hours,” he said. “Worst case scenario, the next day.”
“What if there's a regression and they can't find it?” I thought to myself but refused to ask. There had already been a lot of time wasted, and I knew by the nested conditionals that it would take at least 2^8 manual tests to be 100 percent sure it was all fine, and no QA would ever test it enough.
Automated UI tests were also in place but took too long to run. The tests would run once a day, overnight, and it was impossible to cover that many possible scenarios in that amount of time.
“No,” I finally said. “That's too expensive for the business. We need a better solution.”
He told me to subclass the WebViewController and override the behavior I wanted, and then to fire fatalErrors for the methods I didn't want called. But that's a total violation of the Liskov Substitution Principle, the L in SOLID.
Another senior engineer overheard our conversation and noticed how perplexed I was and kindly offered another solution to the problem.
“We're tired of dealing with regressions on that class,” he said. “Just don't touch it! Create a new WebViewController from scratch specifically for that feature.”
This suggestion was appealing; it sounded like the right thing to do. The complexity of the class would just increase by adding another boolean or creating a subclass. However, creating a new class to do almost the same thing was something I didn't want to do since it would increase the size of the project.
Our conversation answered a lot of questions I had about that codebase, such as: Why are there 20+ developers working on this codebase and the velocity is so low? Why are there so many classes? Why does the suite of tests take ten minutes to run? Why is no one doing TDD? Why are there so many regressions? Why after why after why.
And the most important question I had was “How can I help?”
By finishing the feature as quickly as possible and moving on to another task? I don't think so.
There's a much better opportunity there. My job is to provide value. A solution with no regressions. Fast and small iterations. Fewer people involved. Not as many meetings. Reduced cost. Improved efficiency. Great software.
In short, my job is to make the work of everyone around me more enjoyable by delivering great features to our customers and helping the business profit.
“It’s time to fix that Swift class,” I said to myself.
I spoke with my manager and he agreed that I should take the extra time required to fix these problems, since that part of the application would otherwise generate hours and hours of wasted work and QA every month.
I took the chance to get a junior developer to pair with. Together we started developing a solution following TDD, something he was keen to learn. Little by little, we wrote a presentation class, an error handler, a controller for the business logic, and a view controller agnostic from navigation, layout, and decision making.
That modular approach gave us confidence and control over our work. It allowed us to plug different component combinations in and out. It gave us the freedom to choose from, for example, a local URL controller with a loading spinner UI or another implementation that showed a string like Loading on the screen — we could make it appear however the designers wanted it to appear. It could be in a navigation controller or it could be presented modally. And we could easily track errors to a reporting system without changing any code that wasn’t related to error handling. In short, we could do anything we wanted.
The next day, I was invited to a stand-up with another team, where I learned someone would start working on a similar task. I quickly shared I had a working component that could function out of the box, and together we finished the implementation in half a day. In turn, this earned me a lot of points with the developers and managers of this other team.
As a direct result, people in the company saw how valuable my work was and began inviting me to consult on engineering matters. This quickly secured my place as an essential developer within the company.
Building features that work is my responsibility, and not that of the QA team. The QA team can help guarantee that we’re delivering good quality features that match the business and customer expectations. We can achieve that together.
You can start right now with small actions. Improve a class that’s getting in the way of everybody. Make the CI server faster. Fix your project warnings. Automate time consuming tasks. Doing such thing will provide immediate and tremendous value.