Spaghetti code is one of those things that you’ll eventually encounter in some form. To the end-user, everything may appear fine. But to the developer, it can be a horrible nightmare to deal with. Personally, I’ve been on the receiving end of spaghetti code in multiple projects. From what I’ve learned, spaghetti code is often the root cause of bugs and the vulnerabilities it exposes.
But before we go any further, we need to understand what spaghetti code is.
What Is Spaghetti Code?
Over the years, I’ve come to understand that code is more than just code. It’s a translation of ideas into a digitized format. It is also a language consumed by fellow developers. This means that having a piece of software that works is not enough. It needs to be accessible to other developers who might inherit it after you’ve left the project or organization.
Spaghetti code is when the code you work with is unstructured by nature, tightly coupled, and contains an unnecessary amount of mental translation between reality and its representations.
The issue with spaghetti code is that the lines composed for the software are not easy to mentally digest. As a developer, you need to trace a particular idea across multiple classes, functions, modules, and or files. Tracking down all the parts is difficult because the only consistent thing about the code is its inconsistency.
This process is frustrating, especially when it is linked to a bug. What’s worse is that spaghetti code is often accompanied by a lack of testing units, meaning that common vulnerabilities like script injections might not be handled at all.
The Signs, The Symptoms, And How To Fix It
Here are the common signs and symptoms of spaghetti code and how to fix it.
Programming is still a language. This means that it needs to communicate clearly what it aims to represent. For developers, naming things is a challenge requiring the balance between being highly specific and casting a wide net for future extensions.
However, sometimes the net is simply too wide and the name becomes too vague. When a developer needs to mentally map between the object, functions, named item and the representation, it is usually a symptom of spaghetti code.
For example, you might have an app that services a class of Customers. But in the code, Customers are referred to as Users because you want to capture all the potential subclasses in one place. While this appears innocent enough, it can create a vulnerability if Users also captures the Admin groups. A leakage in the code can allow a malicious user posing as a customer to access the restricted information.
Not only that, because everything is lumped into Users, if you have another category like Workers, changing one thing under the Users umbrella can also negatively impact the Customer or Admin groups.
How do you fix this?
You start by creating clear boundaries around what your name represents. Don’t try to cast too wide a net with your naming. Code what you see. The more simplified your code is, the easier it is to track and trace bugs. In addition, your vulnerabilities are ring-fenced off the specific class, module, or function. If not, it is easier to track down through patterns than it is trying to unravel and map names against what they are to the end-user and what you actually see as a developer.
Lack of patterns
Sometimes, code is created without any patterns attached to it. This is because the developer might be coding without any clear path or planning. They are just writing code to fix an immediate issue and then move on to the next without creating any connections or bridges.
A major risk that arises from this is that duplicate code can occur. This can result in conflicts in various areas such as states and data processing. When this happens, a malicious user might figure out overlaps in the code and exploit them.
How do you fix this?
Different coding languages will have different implementation patterns. However, there are four main categories that apply to object-oriented languages. They are creational, structural, behavioral, and concurrency patterns.
When you identify and start applying these patterns to your code, it also creates a cohesive way to communicate between developers what the intention and purpose of the code is. Spaghetti code has a habit of just being standalone bits of code that may or may not link up with anything else in the repository. You never know if changing one thing will impact negatively on another class, function, or module. But if you use patterns, you give your future self and other developers the ability to trace the impact of the changes. Bridge and adapter patterns are often good solutions for creating links between new and old code without adding to the complexity of preexisting spaghetti code.
Over abstraction happens because the developer is too eager to capture every possible future development that may occur. This can lead to bad naming and code definitions that are so vague that it takes valuable time for future developers to decrypt.
When a developer needs to figure out the code, then it’s usually a sign that you’ve failed at the task of creating clarity. When clarity is sidelined for the sake of an unknown future, it can lead to bad design choices. It also creates a task for future developers to inherit, to figure out what exactly the original coder had in mind.
When you over-abstract your code, you are expecting it to be a certain shape by the end of the entire process — except that shape may change due to various circumstances and non-code related decisions.
How do you fix this?
To avoid creating spaghetti code, start by coding to the conditions. If you’re working in sprints, create your code based on the current sprint. When it comes to agile projects, the future is always in flux. Marketing, commercial, or the boss might change their mind for a project that’s planned two years down the line.
Don’t code for two years in the future. Rather, code for the now. This will allow your code to organically grow in a structurally sound manner. It also keeps you from being tied into implemented ideas that may never materialize.
If you find that your code has grown to a point where your current structures are no longer supporting it, this will be the opportunity to refactor. Refactoring is like pruning your code. It allows you to reshape what you’ve currently got. However, if you try to grow it into a particular shape without any pruning, it can turn into a confusing heap of curly brackets. The latter method is what over-abstraction does to your code.
There’s More To It Than Just Clean Code
It’s easy to say that in order to prevent spaghetti code, you need to write clean code. But the idea of clean code itself can be hard to define. Another term that often gets thrown around is elegant code. Clean and elegant code are just different ways of saying write your code for humans.
When it comes to software development, most of our time is actually spent trying to understand the ideas we want to represent. Code is the manifestation of this representation. To prevent spaghetti code from occurring, you need to detangle your thoughts and arrange your code to match it.
A truly clean codebase is when the core logic of the application is easily accessible by non-natives of the language. This is important because as developers, we look for patterns. When the good groundwork is laid by the previous developer, incoming developers can easily pick up and follow the pattern. This can prevent vulnerabilities and future major failures in the code because developers are not tangled into the high learning curve required to fix, add, or modify existing features.
When we are faced with a high learning curve in a codebase, coupled with time-based delivery requirements, it becomes easier to add something on the side with an override. When you have enough overrides, it can degrade your entire application to a point where adding anything else can potentially break one of the overrides of overrides. Spaghetti code is formed as a result because you simply cannot tell where the code begins or ends.
Start With The Forms
It can be hard to tackle spaghetti code, especially if you inherited it. In part, it is because of the sheer size, unknown and wide-reaching implications. In addition to this, you are also most likely expected to continue adding to the application and deliver features. Every developer that encounters spaghetti code always wants to wipe the slate clean and begin again but this isn’t always possible.
If you’re really stuck for a starting point, begin your spaghetti code unraveling process at your forms. Forms are one of the most common methods of attack through code, command, and SQL injections. A form allows a user to send data over to your application and if it is not secure, it can wreak havoc on your database, reveal unauthorized information, or inject your application with malicious content.
This can be easily prevented by securing your forms in a modular manner. To do this, you can create a series of new interfaces that centralize each form and data component services. This will allow you to begin constructing modular structures with properly enforced validations that are standardized across the application.
Here is an example of what this structure can look like:
Once your forms are structurally sound, you can plug them back into where the original form code is. While it may feel like you’re just adding to the spaghetti, sometimes a complete rewrite isn’t possible and a slow migration is the best alternative.
No one likes inheriting spaghetti code but this is a common experience for many developers. The application may appear to be functionally fine on the surface. But once you get into the code, it becomes extremely hard to work with. The major issue here is that it is hard to test and ensure that your code is as robust as it looks to normal users.
Spaghetti code leads to vulnerabilities in your database and threatens the security of your data. Its tightly coupled nature can cause a cascading effect of unwanted security risks. So start off by securing your forms. Then work on creating abstractions that fit with your current use cases.
You can use bridge and adapter patterns to create links between your new and old code. This can also be applied to structural cleanup tasks like renaming and redefining classes, modules, extensions, and functions.
Those were the basics to dealing with spaghetti code. I hope you enjoyed this piece and thank you for reading.