6 ms·
The term "spaghetti code" comes from a time when programming languages where far less structured. Older languages allowed you to jump anywhere in the program.
by CoolGuySteve 10y ago
The term "spaghetti code" comes from a time when programming languages where far less structured. Older languages allowed you to jump anywhere in the program. Modern languages don't let you jump outside of the function scope and have control constructs like "while".
I'd argue the modern equivalent is the overuse of design pattern abstraction where every function has one or two lines in it. The symptom is the same: following the control flow of your program requires a maddening amount of jumping around in your editor, making it difficult to reason about.
There almost seems to be a cognitive dissonance with some people, where the terms "GOTOs considered harmful" and "spaghetti code" are thrown around but they see nothing wrong with hundreds of tiny functions that are only called from one place. It annoys me that the original point of those terms, that code is for humans, is lost.
With regards to the article's example, refactoring your code for unit tests is making life easier for the computer, not humans. And maybe that's why Bill is annoyed, being human and all.
And don't quote essays you didn't read.
edit: I removed a line but here was the gist of it: Unit tests are great but many languages don't support them well, especially compiled ones. We need to strive for a language that let's you isolate a module for testing without the type system or linker throwing a fit.
- bubbleRefuge 10y agoInteresting point that I've never heard b4. What about the DRY concept?
- jszymborski 10y agoInteresting... Not knowing this background, I always assumed spaghetti code referred to the opposite in modern code: too many nested blocks. I'm always super afraid of growing mounds of logic, and my refactoring usually abstracts a lot of that logic out. If you use a good IDE like IDEA/Jetbrains, you can ease the pain of those "one-line functions" with "click to navigate" function/variable names
- yoz-y 10y agoFor me spaghetti code is called so because when you start pulling on one string of the code you end up spilling everything. A 5-layered for/if statement does not have this property but using abstractions everywhere does.
- smonff 10y ago> For me spaghetti code is called so because when you start pulling on one string of the code you end up spilling everything. A 5-layered for/if statement does not have this property but using abstractions everywhere does. So in theory a well unit-tested spaghetti-code would make you more relaxed because it would assert that spaghetti A interacts with spaghetti B in the C way.
- yoz-y 10y ago> So in theory a well unit-tested spaghetti-code would make you more relaxed because it would assert that spaghetti A interacts with spaghetti B in the C way. I don't follow. In my experience well-unit-tested and spaghetti code are mutually exclusive as the spaghetti code does not have separation of concerns. A note on my opinion on abstractions to clarify: I do not think that abstraction is inherently bad, just that starting with the idea that every component has to be generic, each class must have an abstract interface and that code should never ever see into another classes implementation tends to force developers to make so much mental yoga that they end up tying the components together without realising it.
- lfowles 10y ago>I'd argue the modern equivalent is the overuse of design pattern abstraction where every function has one or two lines in it I've heard this described as Lasagna Layered code or Lasagna code. (Of course Copy Pasta is in our lexicon too, mmmmm)
- shawndumas 10y agoravioli code
- laurent123456 10y ago> With regards to the article's example, refactoring your code for unit tests is making life easier for the computer, not humans. I often find that writing programs with testability in mind leads to more self-contained code, with less dependencies and cleaner interfaces. All this tends to make the code easier to reason about. I'd be curious to see an example of code that's good for unit testing but bad for humans.
- YeGoblynQueenne 10y ago>> they see nothing wrong with hundreds of tiny functions that are only called from one place. It annoys me that the original point of those terms, that code is for humans, is lost. I write code that way (hundreds of tiny methods/functions) because I find it easier to read and modify, and therefore maintain and I've largely done this from a junior dev's point of view where maintainance was most of the job I was doing day in, day out. If you find it hard to follow that sort of program flow maybe it's a sign that there's no rule and different people find different types of coding easier to maintain. In which case we're all a bit screwed as we inevitably eventually will have to handle the code of someone who's had the opposite view than ours. On the plus side, that means we don't have to sweat it so badly with the "right" way to write code and just accept that code eventually becomes one big mess with a gigantic, horrible 50-case switch statement in the middle.
- taneq 10y ago> On the plus side, that means we don't have to sweat it so badly with the "right" way to write code and just accept that code eventually becomes one big mess with a gigantic, horrible 50-case switch statement in the middle. No! No no. No it doesn't. Not unless someone makes it that way. Code doesn't have to become awful. It becomes awful when successive generations of maintainers choose quick easy partial-fixes over a bit of refactoring to fix the problem properly. Code wants to be short, simple, clear, and easy to understand. People make it horrible by not caring (or by not seeing patterns).
- smcdow 10y agoOr, more likely, because they're behind the eight ball.
- _lzell 10y ago> they see nothing wrong with hundreds of tiny functions that are only called from one place On almost every codebase I've been brought into, the simple guideline of "do less within your methods/functions" would lead to a higher engineering velocity for the organization. Yes, putting no thought behind the larger structure (e.g. how your public interfaces into/out of components are defined) will lead to too much jumping around, but if the functions are small and not leaking state then this is relatively easily remedied by someone with a systems bent. Small methods also convey to the reader the intent of the code by tying meaning to it through naming (if naming is thought out :)). Large methods, on the other hand, build rigidity into the system and decreases its changeability. In this case, changes to the system necessitate reading and understanding the full implementation before modification. I will concede that I'm using "large methods" as a not-necessarily-true placeholder for "methods that do multiple things" > refactoring your code for unit tests is making life easier for the computer, not humans A machine does not care if you have unit tests. Unit tests are expressly for the human (team!) task of creating software that is maintainable and changeable.
- michaelfeathers 10y agoThe modern equivalent is called 'ravioli code' http://c2.com/cgi/wiki?RavioliCode http://c2.com/cgi/wiki?RavioliCode "thousands of little classes everywhere and no one knows how to find the places where things really happen."
- taneq 10y agoI was going to suggest the term "Polenta code". It's like that, but even smaller pieces. No more than one or two lines of less than 80 characters each (including indentation).
- NoGravitas 10y agoOrzo code? Couscous code?
- Ace17 10y agoShould a well-designed codebase contain special places where "things really happen"? What about the other places then?
- michaelfeathers 10y agoI think the 'ravioli code' moniker is really a slight on good code. It just takes some time to become acclimated to the style of having many smaller pieces. That said, over-engineering is real. Unnecessary layering should go away.
- Pica_soO 10y agoI think the 'spaghetti code' moniker is really a slight on good code. It just takes some time to become acclimated to the style of having one long codeflow path. That said, over-engineering is real. Unnecessary assembly should go away.
- chias 10y agoI had an internship some time ago with a Large Company, and the codebase I was working on was very enterprisey-Java. It was really painful in this regard: an huge number of different classes, many of which were alternatives to each other, and pretty much everything was applied via homebrewed dependency injection. I didn't know this sort of thing had a name, but I think "ravioli code" matches perfectly. One of my first tasks was to make a minor change and after an hour or so I still had no idea where the code was that needed changing. I called my manager over and he spent about 40 minutes trying to find it. He did, eventually. The change I made then took about 10 minutes to write and test. That's 110 minutes of intern time + 40 minutes of manager time for a 10 minute fix.
- xorgar831 10y ago"Magic code" is the modern "spaghetti code", where you still can't figure out what's going on unless you're deeply knowledgable about 10 layered frameworks that have invisible global state and the entire app magically appears from an annotation.
- rogerdpack 10y agoI was going to say the same thing, ex: with rails callbacks...it's hard to know exactly what is going to happen LOL.
- jnbiche 10y ago> I'd argue the modern equivalent is the overuse of design pattern abstraction where every function has one or two lines in it. If you're writing functional code, very short functions are in fact considered best practice. This is because (obviously!) the basic unit of composition in functional languages is the function, and not the class. So you want to be able to compose those functions as much as possible. In fact, I've also read and been told by old school programmers (I'm 40, so I'm talking 50-60 year-old C hackers) that even non-functional languages like C and Go should limit the length of functions as much as possible. Experience backs this up, since some of the worst code I've ever worked had functions 2-3 screen lengths long, often with unrelated logic there within. Invariably, this was either C or VisualBasic. Say what you want about Java, and it is verbose, but at least it limits programmers to 1 public class per file, and Java programmers do tend to break their class logic into smaller methods. So for imperative languages, the advice is frequently: don't limit functions to a line or two (although that's entirely appropriate and even recommended for Haskell and functional JavaScript), but preferably not more than a dozen or two lines. And if you name your functions well, you usually shouldn't be needing to jump all over the screen to lookup a function. The name should tell you exactly what it does. If you're using statically typed languages with a decent IDE, the type signature should provide any additional needed info. Google's C++ style guide: "Prefer small and focused functions." Haskell.org's style guide: "Most haskell functions should be at most a few lines, only case expression over large data types (that should be avoided, too) may need corresponding space." felixge's nodejs style guide (one of the more popular ones for NodeJS): "Keep your functions short. A good function fits on a slide that the people in the last row of a big room can comfortably read. So don't count on them having perfect vision and limit yourself to ~15 lines of code per function." (my experience with functional JS is that the best code bases strive to keep functions around 5-6 lines in most cases; check out React, which for any criticisms is very well-written).
- deleted 10y ago[deleted]
- mhomde 10y agoWell that's one harmful pattern, but by no means the only one, you also have "The Blob". I once worked on a project where the code in general was pretty good but the programmers had seemingly no knowledge of design patterns at all. Code was contained in these giant classes, they actually used partial files with underscores in the naming to segment it, ugh. Took quite some effort to disentangle that. As with most things no extreme is good, neither Blobs nor Poltergeists :)
- douche 10y agoHoly hell, partial classes. I'm not convinced that, outside of WinForms designer-generated control definitions, there is ever a legitimate reason to use them. I just tore apart one of those kinds of monstrosities last week, where the person who originally wrote it, went so far as to do C-style name prefixing on the methods, i.e. Foo_DoThing(), Bar_DoOtherThing(), Foo_DoOtherThing(), but left it all in one honking monster class instead of splitting it out.
- Silures 10y agoI agree. Partial classes are for supplementing machine-generated code: winforms designer files, generated entity framework classes etc. That is, wherever the IDE would overwrite any edits you made. I'm currently refactoring a 3 partial class monster usercontrol: 238 lines, 2370 lines, and 4526 lines. Fun.
- mhomde 10y agoYeah... they're really handy for separating generated code from user code... but having to segment classes with them is a rather foul code smell :) Haha, wow, that's another way to go I guess. It never ceases to amaze me how inexperienced programmers can brute force problems with the limited tools they possess :)
- zzzcpan 10y agoMost people don't have even the most basic understanding of how they reason about and write code. So they just follow "best" practices and other kinds of dogma. Nothing wrong with that. But it does make it harder to have a meaningful discussion about "bad" code and how to improve things. And I do think there is a room for a tenfold improvement in reliability/productivity compared to OOP.
- nnq 10y ago> With regards to the article's example, refactoring your code for unit tests is making life easier for the computer, not humans. And maybe that's why Bill is annoyed, being human and all. Maybe the tradeoff is actually between: A. short term productivity / speed of development -- use longer functions without factoring out non-repeating code excessively B. long term maintainability -- short and easily testable functions are definitely better So use approach A. to code an MVP and approach B. for a decade-lasting enterprise application on whose development tens of people will rotate? Oh, and... please, if you language allows you, use closures / functions inside function when you factor out a small bit of code that does not repeat, NOT private methods or non-exported functions! This increases code locality it's obvious that one line helper is used only once. And of course, give the mini-functions meaningful names, otherwise there is no point in doing this in the first place - you only factor out to increase readability. And prevent the mini-helpers from using variables from outer scope too much (if your mini-helper uses 5 outer-scope variables, then DON't factor it into a mini-helper). (Advice above is extracted from a talk inspired by smth J. Carmack wrote, but can't remember exactly what...)
- deadowl 10y agoWhat about the code for this python regular expression engine that is filled with goto statements? https://github.com/python/cpython/blob/master/Modules/sre_lib.h#L1154 https://github.com/python/cpython/blob/master/Modules/sre_li... Or the code for the OpenSSL handshake that interleaves preprocessing directives? https://github.com/openssl/openssl/blob/master/test/handshake_helper.c#L376 https://github.com/openssl/openssl/blob/master/test/handshak...