Add complete, 24/7 vet care
One time Fuzzy consult
I have often noticed myself ignoring basic yet essential computer science concepts throughout my career. I hear and have said far too often that iOS development does not have the same complexity as other types of engineering.I was wrong, and if you share this opinion I would encourage you to reconsider. You should not strive to over-engineer your app, but you should do your best to apply these basic guidelines in their most simple form. You will notice a considerable difference in the codebase you're working in.
SOLID is an acronym that stands for:
S: Single responsibility principle
O: Open–closed principle
L: Liskov substitution principle
I: Interface segregation principle
D: Dependency inversion principle
We will just be covering the Single responsibility principle in this article.
S in SOLID
The S represents the Single Responsibility Principle. It was introduced by Robert C. Martin(aka. Uncle Bob). It means exactly what it says. Every object, module, and function you create should only have one responsibility. Do keep in mind these are guidelines, not rules.
Okay, but why…
The more responsibility something has, the more complex it becomes to maintain by you and your team.
It becomes less reusable. This is due to high coupling and often leads to rigid code. It also becomes easier to introduce more bugs due to an increase in side effects that scale with the increase in responsibilities.
It makes the implementation explicit and understood by everyone
Take a look at the example below,
If we recall the SRP principle, every class should have a single responsibility. So, let's review each responsibility the type has currently.
Persisting the User's information
Logging save events
Let's look at how we could potentially simplify the User's struct. I've come up with the following.
Here I've created a protocol called AgeCalulator. This may seem like a small change, but you have to remember that Swift encourages composition(pieces you can put together). So, if we were to make another object called Animal, we could reuse our AgeCalulator somewhere else.
Next, I've extracted our networking dependency from our User object altogether. By creating a UserController, I've moved the responsibility from our User object to our controller object.
Last, I've removed the responsibility of our analytics event tracking. You may have noticed it's been removed entirely. It isn't the User or user controller's job to track events. You may be asking who should be responsible for it, which is exactly the question you should be asking. I'll elaborate more in another article.
Now I know the above example is a very simple example. So let’s take a look at a more complex example, something that you may have even done yourself in the past.
For some context, let's consider that we are building the following feature in our app:
We want our users to be able to track events on a day-to-day basis. To start we want our users to be able to log their exercises, meals, and weight.
When tracking Weight we need to be able to track a number
When tracking a meal we need to be able to track the Name, Amount, and the Unit of Measurement
When tracking an exercise we want to be able to track the duration of the exercise as well as the type of exercise.
We need to track every event on our backend
Below I've created our data models
Below is a not-so-uncommon example of how someone might right this class.
If we recall the SRP principle, every class should have a single responsibility. So, let's review each responsibility the TrackerViewController currently has.
Setting up the UI
Saving an event
Handling networking errors
Managing state based on the type of tracker
Navigation to the success screen
It is not super uncommon for a view controller to ultimately end up housing a multitude of implementation details, and can often lead to view controller classes with 100s if not 1000s of lines of code.
Here is an example of how we could potentially break up the logic into something a little more manageable.
Here are a few protocols I made to make the implementation a little better.
Next, I break apart TrackerViewController into their own respective classes and then opt to subclass it.
Okay, what I've done is incredibly simple.
I made a few protocols to allow us to have a reusable postTrackableEvents method
I created an APIClient, giving the responsibility of networking to our APIClient instead of our TrackerViewController
I subclassed TrackerViewController and created three new trackers view controllers. MealTrackerViewController, WeightTrackerViewController, and ExerciseViewController.
This simple change makes every view controller have a single responsibility, abstracting all knowledge of the different types of trackers there are, removing redundant networking requests, and removing having a bloated file full of UI components.
P.S. I could have created a ViewModel but we will save that for a different article.
The more you can remove responsibilities from your class, the easier it becomes to read and maintain.
Hopefully, you found this useful and that you are able to take your code to the next level by remembering the Single Responsibility Principle.
To view, copy, and customize code on your own view this post on Engineering.Fuzzy.com on Hashnode.