Users don’t really care about how complex your code is as long as it works and show things nicely. You can write the whole Twitter in one kilometric line and the user won’t even notice such blasphemy. However, you, as developer as well as product manager should be careful about how complex is the code you’re creating. Let’s see some reasons.
Why don’t we want complex code?
Let’s list some technical reasons:
- It’s more complex to read. Reading and understanding the code is a requirement in order to be able to modify it correctly.
- It’s more complex to modify. Any change we have to make over that piece of code implies that you must know what the code must do and how it must behave to any situation. The more complex the code, the more difficult will be to predict the consecuences of your change.
- It’s more complex to test. The number of required tests to verify the code works correctly increase. This usually implies the tests might not be detailed enough in exchange of covering a larger portion of the functionality.
There are also some management reasons to take into account:
- The complex code becomes a black box which nobody knows what it does exactly.
- The bugs that appear inside the complex code won’t be fixed or if they will, there is a high risk of new bugs to appear.
- The new features need to make “workarounds” over the bugs that the complex code might have (if we suppose those bugs won’t be fixed inside that complex code). This code generate a chain reaction in the sense of those “workarounds” add an unneeded complexity over that new feature.
- If the code needs changes (maybe additional support or better performance) you might need to remake that piece from scratch, with the corresponding cost overrun.
How can we detect complex code?
Here you have some simple ideas to detect potentially problematic code:
- Functions, classes and / or files excessively large. Functions over 50 lines of code, classes with more than 10 or 15 methods or files over 900 lines usually are indicators of something going wrong. Does that function do just one task or it does more things? Does that class have only one responsability or it does the actions of several possible components?.
- Large number of parameters in a function. A reasonable limit is usually between 5 and 7 parameters. Again, if you need to use more parameters, maybe you’re doing something wrong. Do you need all those parameters or maybe you need an object?
- Large number of dependencies for an object. This have the same reasoning as the previous point.
If we check a bit technical concept, the cyclomatic complexity give us a good approximation of how complex is our code.
Being simple, the cyclomatic complexity counts the number of “if” sentences or conditions that a function has. The formal definition counts the number of independent paths that a function has. The bigger the value, the more complex that function is.
To give an idea, for functions, values lower than 5-7 are a good sign, but values higher than 10 we should consider to change the code. This same idea can be extended to classes, packages or even applications.
Simple solutions for a complex code.
Without considering complex solutions implying a big code refactor, the easier solutions imply moving the code to another place.
- You can split big functions into smaller ones. These small functions will do a small part of the original function.
- If a class has a large number of functions, it should be possible to group some of those functions and move them to a more specialized class. Sometimes this isn’t possible, but sometimes the class assumes more responsabilities than it should be responsible for, and this cause the increase in the number of methods of the class. In any case, this bullet is based on that a class should only have one responsability; if that class needs 30 methods in order to fulfill such responsability, then there is nothing to do.
- In a similar way, if a class has a large number of dependencies, it might be possible that those dependencies can be grouped in order to make our code to depend on just a few objects.
For example, a function can validate the parameters, call several methods of another class, do several additional checks to verify the previous calls worked properly, send notifications or events and finally return a value. Each of these steps can hide a very big complexity, but our function will be easier to handle.
Another example: a class could depend, among other things, on a class to validate emails, one or more email providers (which they require configuration), and a class send the email through the configured provider. It should be possible to create a class that groups and depends on those 3 components, and at the same time provides an easy interface to send emails. Our class would depend on this new class instead of the 3 others.