It seems like when people talk about inheriting a bad codebase people often mention: spaghetti code, not making use of language patterns like inheritance/interfaces/modules/etc, no or bad documentation.
To be honest, besides the documentation thing idgaf about stuff like this. If the code works it works and I can still fix or improve it slowly over time incrementally. What I care about instead:
- Obscure libraries or dependencies. I have seen this so often that old code uses obscure dependencies without updates since 5y when it could have used apache or pandas or stuff. When there are bugs or security problems here refactoring
- No or few unit tests. Refactoring code is hard when I can't be sure about all cases and exceptions. If you work on something with inexperienced or bad programmers - please tell them to write more effing tests instead of "pretty" code
- Non-migratable custom stuff. If your program absolutely needs a custom build pipeline (I hate this concept in general but idk not here to judge, maybe you need it) at least choose something that can be migrated to a new technology in 10 years. e.g. Javascript build script or bash yes - some weird building.xml file that only works in eclipse no no no
What are your thoughts on this subject? Ever inherited a 10yo codebase that seemed like a black hole? Ever had to refactor spaghetti code? Disagree with me which of these is the worst or doesn't matter?
Your list so far is good and I agree with those. But here are some additional things that particularly irk me.
These are some of the things that really annoy me, though there's probably a lot more. It just smells bad.
To be honest, if you look at code you wrote 10 years ago you'll probably find stuff like that ;)
I agree, this is annoying most of the time.
But the thing I run into a lot and really can't stand is premature abstraction: "These 2 things look the same. Let's build a package with some base class so that everything is DRY"
Some time later, it turns out that the 2 things aren't remotely the same. Meanwhile, layers upon layers of crap have been built with this package as a dependency and I've got to trawl through it, tease the 2 things apart, refactor anything that uses the package, and/or convince other teams/users that they need to do the same in their code. Not fun
I forgot about that but as soon as you referenced bugs twice it sparked memories lmfao
Humans are good at suspecting ;)
I once saw a PowerPoint created to explain how shit an inherited codebase was. Lots of stuff you mentioned, duplicated methods, breaking abstraction layers all over the place, stuff like that. I felt bad for the original owners being called out like that, but they also kinda deserved it lol
I'm not a software engineer but a data scientist.
But I can tell you that in the data world, unit tests are not yet that common practice. But it really should be. People string together SQL queries that give insensible output and no one is checking, at least not as part of regular workflow.
I'm part-timer so not really able to push for culture change on this at my org, but something i've noticed and it causes problems for sure
Are you sure about that? Or is it just the industry or company you're in?
I personally like code to be easy to understand, but I also agree with all your other points.
I think a lot of this overhyped. The easiest to understand code has minimally viable abstraction.
Here are some things that are not necessarily always bad but that I think smell:
I consider shit code, assuming it works, to be hard to read and hard to build. If I have to read every line of a function/method to figure out what it does, it probably means the name of the function/method is inadequate at explaining it. If I have to build something that only works on a certain operating system, or has dependencies that only work on computers with specific local files, it's shitty code.
Premature optimization also makes for shitty code. One code snippet I remember used custom bitwise logic for multiplication. It was both a wasted effort (because of compiler optimizations), and made the code unnecessarily complex for newer junior devs on the team to understand.
Cleverness is fine imo, as long as the clever implementation is behind a good interface that can be swapped out easily if problems arise. I would prefer to start off with a well-abstracted dumb implementation and swap it for a clever one afterwards so that the commit history can be called upon for easy file reverts.