By Andrew Rousseau. Published on March 10, 2023.
In software engineering the projects we work on grow very organically. We begin with a set of requirements and perhaps if we're being thoughtful we do a bit of design planning as well. From there the structure of the codebase can grow with a mind of its own. As we move from feature to feature we try to keep an eye on organization but often we're moving too quickly to go back and clean up after ourselves.
The result of this additive approach to coding is that we often end up with various types of complexity. This complexity comes in many shapes and sizes. It may be that methods grow out of control in size, scope and logic. We'll explore below one of those forms of complexity, detail complexity.
Detail complexity in software engineering is when we have many elements. I’m sure we’ve all experienced this type of complexity before. There are those methods in some codebases that you have to scroll on and on only to lose track of all the variables and logic at work there. When we have too many moving parts in lengthy methods we have a high level of detail complexity.
Lots of method arguments
Most methods start with just a few arguments, but as we continue to code the amount of arguments can unintentionally increase. This can make a method that started simple much more complex to follow the logic in. The cognitive load of having to keep track of so many variables is a good example of detail complexity.
Having too many arguments in a given method is typically a sign that we're trying to accomplish too much at once. One way to avoid this pitfall is to split the function into multiple functions if the amount of arguments needed becomes too large. Finding the natural breaks in functionality and keeping each method confined to a single effect often helps alleviate this form of detail complexity.
Methods that are very lengthy
Even when a method appears to have very few arguments, it can grow in complexity by length. Again this tends to happen very organically as we work through the needed logic for our specific use case. The above example is one of my favorites from a code base I've had to work on.
Here the author of this function managed to make one of the arguments an open-ended array of options. As a result the array of options grew over time and the logic pertaining to those options grew to a whopping 646 lines of code. Worse yet, the method isn't even clear on what effect it is having as in the end it returns nothing.
This is an example of detail complexity due to length. A way to avoid this type of complexity is to restrict the length of your function to what would be viewable on the screen at any given time. If the method has grown beyond that it is a good indicator that you are doing too much in a single method. By focusing on each effect you are trying to have you can probably break the single method up into several smaller ones.
Methods with too many moving parts
The last type of detail complexity we’ll look at here involves a method having too many moving parts. While this can come in many forms, the most common is that we have too many conditions inside the method. This leads to too many possible execution paths. We will explore execution paths in more depth in a later post regarding NPath complexity.
For now, a good way to avoid this problem is to limit yourself to just a couple of conditions inside of a given method.The point at which you have reached the third possible execution path you should consider breaking the method up into smaller methods to make it easier to read and maintain.
Hopefully you have found these examples helpful and can now recognize detail complexity. If you avoid writing methods that have too many arguments, are too lengthy, or have too many moving parts in it you will have reduced the amount of detail complexity in your codebase. This should lead to better readability and therefore improved maintainability of the codebase as a whole. Which is something I hope we can all agree is beneficial to everyone that will contribute to your project.