Recently, I finished reading "A Philosophy of Software Design" and in the second chapter, it explores the topic of Software Complexity.
The book "A Philosophy of Software Design" defines complexity practically:
"Complexity is anything related to the structure of a software system that makes it hard to understand and modify."
In other words, complexity can take many forms, and doesn't necessary has anything to do with performance, your code can be performant and still be complex
I'd like to share some key definitions and insights from the book in this article. But first, let's imagine a common situation that probably you’ve already been to…
Let's dive into a horror story many of you have probably experienced or will experience.
It started with a simple CRUD task management app. The code was clean, modular, and easy to maintain. The development team was happy, and the system worked perfectly for initial clients.
Problems began when the sales team sold the system to a large company, claiming it had calendar integration, email notifications, and an amazing report generator. With the sale finalized, these features had to be implemented quickly.
Calendar Integration: The team had to integrate with Google Calendar and Outlook. Different developers implemented the solutions, resulting in inconsistent approaches.
Email Notifications: Email notifications were added next. One developer used a specific library, while another created a custom solution. The mixed approaches made the code confusing.
Report Generator: For the report generator, developers used various technologies: PDFs, Excel exports, and interactive dashboards. The lack of a unified approach made maintenance a nightmare.
Growing Complexity: Each feature was developed in isolation and quickly, leading to dependencies between features. Developers started creating "quick fixes" to make everything work, increasing the system's complexity and coupling.
Software development doesn't happen in a vacuum; various internal and external factors influence it. We've all been, or will be, in a situation like this.
Then the problems began:
It's clear we now have a complex system.
Now let's "dissect" this complexity to make it easier to identify and mitigate it.
Well, "mitigate" means:
"To make less severe, serious, or painful; to alleviate."
I believe complexity is often inherent in code. Some things are complex by nature. Your role as a developer isn't just to create code that the computer can execute efficiently but also to create code that future developers (including your future self) can work with.
“Controlling complexity is the essence of computer programming.”
— Brian Kernighan
The author of the mentioned book states that complexity typically manifests in three ways, which we will explore here.
Change amplification occurs when a seemingly simple change requires modifications in many different places.
For example, if the Product Owner requests a "priority" or "completion date" field and your entities are tightly coupled, how many changes would you need to make?
Cognitive load refers to the amount of knowledge and time a developer needs to complete a task.
So imagine this scenario: A new developer joined the team, he was assigned to fix a bug in the report generator. To complete this task, the dev needed to:
It's the classic "impossible to estimate" scenario, where the task could take one point or eight—better roll a D20 and respond accordingly.
Unknown unknowns are when you don't know what you don't know.
This is the worst manifestation of complexity because you might alter things you shouldn't, causing everything to break.
Example: A developer modified the email-sending code to add a new notification, unaware that it would affect the report generator, which depended on that function. This caused significant issues for clients, exemplifying the worst form of emergent complexity.
Having seen the horror story and the three main symptoms, let's look at what causes complexity.
Dependencies are essential in software and cannot be completely eliminated. They allow different parts of the system to interact and function together. However, dependencies, when not managed properly, can significantly increase complexity.
A dependency exists when code cannot be understood or modified in isolation, requiring consideration or modification of related code.
Obscurity occurs when important information is not obvious. This can make the codebase hard to understand, leading to increased cognitive load and the risk of unknown unknowns.
Obscurity occurs when important information is not obvious.
Because it is incremental, it's easy to think, "just this once, it won't matter." But when accumulated, fixing one or two dependencies alone won't make much difference.
“Everything is a tradeoff in software engineering.”
— I don't remember the author
I could write a lot of rules, strategies, and frameworks that you probably already saw on the internet on how to avoid complexity: SOLID, Design Patterns, YAGNI, KISS, etc.
However, you can unify them all into one guiding principle (as mentioned in "The Pragmatic Programmer."): "Is what I am implementing easy to change?" If the answer is no, then you are probably increasing complexity.
Ensuring your code is easy to change simplifies maintenance, reduces cognitive load on developers, and makes the system more adaptable and less error-prone.
Thank You!
The above is the detailed content of The Never Ending Battle Against Software Complexity. For more information, please follow other related articles on the PHP Chinese website!