Many times, in discussions about programming paradigms and styles or performance, people thorow this word like everybody agrees on what spaghetti code is! Definitions really matter in these situations and if people do not agree on them, even good faith arguments won't go anywhere. Most people throwing this usually lack knowledge or understanding of the problem and are just defending a position with some memorized word salad, but it is not always the case.
I started to think deeply on what spaghetti code is because I did not think of it much after reading about goto statements and the famous dijkstra paper when I was a student more than 13-14 years ago. Obviously, a code full of gotos which don't know any boundaries which can jump from any place to any other have a mental model of a spaghetti and it is not delicious at all. No offence to Italian people. I love spaghetti. I do not have the ability to understand and work with spaghetti code.
Even based on the obvious worst case which no longer has relevance, we can see spaghetti code is some code which has lots of jumps from unexpected places to unexpected places. I could say complex/hard to understand jumps as well. Many is easy to understand, more jumps is worse than less jumps. However, what is unexpected, hard to understand or complex when it comes to a jump?
When you read a code and you cannot guess why you are there in the flow of the program or where it might go to, then it is complex, and you cannot understand/expect where it goes. conditions, try catch structures, gotos and virtual functions all are obvious things which can jump the code to some place. switch case statements, function calls and if statements are more explicit structures which understanding their jump behavior is relatively simpler. It is relatively simple because usually you don't need to look at a place far from where you are reading to understand why this jump might/might not happen.
However a virtual function call or a goto or a try catch, necessarely needs more effort to be understood.
From the above you can deduce that if you don't know if a statement might jump or not and it does not always yield the same resultt, then it is more complex. A try's body always don't go to the catch part and a virtual method call goes to somewhere based on the type of object which has been decided somewhere else. A goto can become hard to understand because it is easy to abuse. it is usually followed from a set of conditions, and you find multiple of them close to each other. However a ordinary function call, always goes to the same place and an if statement always goes in or not and the condition is easy to check even if you always cannot fully understand why a variable has a specific value. that is a separate concern in your head and can be thought about separately. The jump behavior is easy to understand and does not require any mental jujitsu to be understood. You know when it will happen if you don't know exactly why. It is easy to find out what you should inspect/observe to understand the why.
Now if we go one level back, we'll see that in order to understand a program, you need to have its data flow in your head in a mental model so having jumps which you cannot understand and predict in your head easily, makes creating this mental model harder or in the case of gotos of 60 years ago, near impossible. When you know in what situation you can go to a line of a program, then it is easier to understand compared to when you don't know that. Those gotos were hard to understand because of their interwoven nature. You could not easily understand which gotos are followed which ended up you are being at line x. try catches and virtual calls are like that, but a big switch statement is not.
What makes virtual calls a cause of spaghetti code is the fact that they are easy to abuse as well. You don't know which object and when calls something which is virtual and when the type of the virtual call is decided. Abuse of things like dependency injection can make this worse.
When the reasons of a jump are decided from from where it happens and it is not easy to understand where you should look in to, to understand them, then the jump is hard to understand and if you have many of these interwoven, then the code is called spaghetti code. usually, you have interwoven jumps when you did not structure your operations well and you don't have a good architecture. these days with all the structures we have, it is easy to not intentionally create interwoven jumps and people usually do that if either they are in their first few years of programming or if they are trying to follow some principles like single responsibility or domain driven design and as a result decide that for example functionality x has to be in class y and class z cannot implement or call it no matter what.
This is my definition of spaghetti code and I would really like to get feedback from others on what they think spaghetti code is or where and why I am wrong.