Imagine you’re reading a novel. It has a deep story, rich events and well-written characters with deep personalities. However, the novel was written in one long block with no paragraphs, no punctuation and no line breaks. It’s hard to understand where one sentence ends and another begins.
That sounds like a nightmare, right? As amazing as the story is, much of the novel’s value is lost because the writing lacks structure.
Now, imagine writing software that’s just as hard to understand. It could work perfectly with no bugs and an excellent user experience. However, when the code is jammed into a single file with limited methods and cryptic property or method names, it becomes hard to determine where an operation begins and what, specifically, it does. Extending such a codebase with more features is frustrating at best — and impossible at worst.
One situation where you might encounter overly complicated code is when a single piece of code is responsible for too many actions, complicating the generation of the final output.
For example, imagine a function that adds two numbers. It performs the addition, converts the value to a string, gets the localized presentation of this string, then styles the result by changing the colors and font. It passes the result to different layers of UI, opens a file on disk that contains all previously calculated math operations, counts the existing entries, creates a new file if the existing operation is more than 100, creates a new string with the addition operation with its result and timestamp, appends this new entry to the file and, finally, saves the file.
Wow, even just explaining all of this was a workout! I wouldn’t be surprised if the code was hard to follow.
The above method has a very obvious problem: It’s doing too many things. Some of its tasks are directly related to presenting the result on screen, while others are related to logging. The part that’s directly related to presenting the result on screen can obviously use a cleanup, but you’ll focus first on the part that wasn’t part of the output: The part that opens a log file and checks if there are too many entries in that file before adding the operation to that file.
This operation can be described as a side effect. It’s a change in the state of the system that isn’t directly related to the intended output of the original operation. The user doesn’t care about the log file! They only care about the output that appears on the screen. Any operation or changes of stored values or state of the system that isn’t part of producing the output is a side effect.
Side effects aren’t a bad thing, but if left unchecked and not properly organized, they can make your system overly complicated and harder to test.
When you implemented the observer pattern, you made the contacts book register itself as an observer to every contact. Additionally, if you modify any of the stored contacts, the contacts book that contains those entries will be automatically updated.
Both of those tasks are actually side effects. The first method’s main focus is to save the contact in its list. Observing changes is not part of the main operation, and is, therefore, a side-effect.
Similarly, changing one of a contact’s values is the main operation; notifying a list of observers that the contact has changed isn’t related to the update operation. Thus, it’s a side effect.
Side effects are not “bad”. In the contacts example, the side effects are crucial to the system. The way you implement your code will determine how side effects work and how many of them you’ll have. You can implement all UI updates on your app as side effects. As long as they’re organized and consistent, your system will be easy to maintain.
Now that you understand what side effects in software engineering are, you can now learn the famous SOLID principle.
A little bit of history about those principles: In 2000, Robert Cecil Martin (known as Uncle Bob) first introduced the term SOLID in his paper “Design Principles and Design Patterns”. The term is an acronym for five design principles that address the problem of software rotting: a metaphor for how a poorly structured codebase can deteriorate with time as it receives more features and its complexity increases.
The five SOLID principles are:
S: Single Responsibility.
O: Open-Closed principle.
L: Liskov Substitution.
I: Interface Segregation.
D: Dependency Inversion.
Those principles are similar to design patterns because both concepts solve a problem. Don’t think of them as medication but rather as vitamins that improve your system. Each of those guidelines can help shape your design in a way that can make it easier to extend in the future.
In this lesson and the next, you’ll take a closer look at each of the SOLID principles.
See forum comments
This content was released on Oct 17 2023. The official support period is 6-months
from this date.
Download course materials from Github
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress,
bookmark, personalise your learner profile and more!
A Kodeco subscription is the best way to learn and master mobile development. Learn iOS, Swift, Android, Kotlin, Flutter and Dart development and unlock our massive catalog of 50+ books and 4,000+ videos.