s/internet/everything/
I can't tell you how many times I have to reteach myself this lesson. I'm not advocating for writing sloppy/bad code on purpose but I fall into the trap way too often of trying to make my code a work of art or overly-clever. No one cares, don't spend twice the time to try to abstract something within an inch of its life. Write working code and move on. Once you've done something 4, 5, 10, 15 times then you can look for an abstraction.
Really I think I use refactoring or "stressing over the best way" really to just be a way to procrastinate and tell myself I'm doing something useful or dare I say noble. Of course in practice it's often neither.
s/many games/the world/g
Some code may very well be "set and forget" - you write it once and noone ever looks at it ever again. If a project is allocated 100 days, why invest even 1 of those refactoring and polishing code that really doesn't need it?
The difficulty, of course is knowing which code is 'set and forget' and which isn't. I usually avoid refactoring until you hit the same "how does this work again?" wall at least three times.
Now, I'm a bit more careful. There's a pretty famous quote by Sandi Mentz: "Duplication is far cheaper than the wrong abstraction."
https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstracti...
Make sure that you're picking the write way to think about the task at hand, rather than blindly following DRY.
There are times when even a single instance of a code call would be made clearer with abstraction, and there are times where having the same piece of code duplicated multiple times (or duplicated with one piece changed) is far clearer than trying to abstract it.
This Reddit Comment also has an interesting take: https://www.reddit.com/r/programming/comments/5txp5t/duplica...
> The main purpose of abstractions is not to remove or reduce duplication, and not even to make code "reusable"; it is to make semantic patterns and assumptions explicit and, if possible, first-class.
The further comments provide more discussion.
---
I agree with the rest of your comment that refactoring and code-cleanup should be done in pieces and that, as with everything, striking the right balance is key.
Maybe you know very different programmers than I have known, but if I had to compare what has been a bigger source of problems—missed abstractions, or abstractions that make things more difficult for no benefit, it's definitely bad abstractions almost every time.
Duplication is a problem as the different implementations inevitably drift and get repeated bugs, but simple reduplication results in the rather known problem of RavioliCode() of thousands low cohesion functions. Which ends up unreadable thus bug prone and slow to develop.
Use of the right patterns or rather paradigms reduces amount of code in general, thus reducing duplication.
Wrong patterns are hard to actually change especially on change averse projects. The more widely used the wrong design is, the harder it is to change as the hacks on it multiply. Even worse if the wrong patterns (not code) are duplicated.
These require in depth rewrites to which bosses are usually allergic, which are very hard to pull off on bigger teams too. Incredibly hard to coordinate.
-- () https://wiki.c2.com/?RavioliCode - can happen in functional and structural code too.
You know, it is a bit pretentious to read a few sentences that someone wrote and then conclude a lot about what they know or do not know.
And I actually do know what ravioli code is. I think ravioli code is mostly a good thing. Also the people on the c2.com page are not uniformly negative about it. Not all code should be ravioli code but in a project with complex requirements there should be quite a bit of ravioli code. It is true that ravioli code is not easy to understand if you are a newcomer to a project but really if the context is 'a project with complex requirements' why would anyone think that it is easy to get into, no matter how it is written?
Another thing is that ravioli code absolutely needs automated tests.
'This style maximizes maintainability by old hands at the expense of comprehensibility by newcomers.' Maintainability is exactly what I want maximized. It sounds a bit bad if there are no developers that are there for a long time. But in that case you are cooked anyway, I would say.
Preferable state is high internal cohesion and low coupling. Most code is opposite. Ravioli is when you trade high coupling without introducing cohesion, which is structure. Typical state after dumb refactor rather than rework.
Simple extraction of functions does not get you anything (if done well within a module, maybe increases cohesion), while making them reusable modules trades reduced cohesion for increased coupling, which is bad.
If you deduplicate too much you suddenly cannot change anything simply... As every place that got deduped is now coupled to one implementation. Once you need some special care, you get to replicate it again oftentimes.
Only true primitives are really worth it to not replicate and parts of code that won't change. (Ask the crystal ball again.)
See if deduplicaton gets you any of useful high level designs, like MVC, event-driven, reactive, message based. Without overarching design, you end up with a mass of locally useful ones that together are incompatible thus requiring lots of different, unique glue code.
The big mistake people do is to equate design patterns with code patterns. Which is what the silly GoF book did a lot. For example "fire and forget background parallel tasks" is a design pattern. Reactor (executes Strategies to deal with Events) is one while Singleton or Context are not. Event also is not. Etc. Generally anything that doesn't actually structure anything is a code pattern.
Well, if the “ugly” code works the same as the “pretty” code, but the ugly code can be developed faster than the pretty code, then you should definitely prefer the ugly code: in fact, if this is the case, the ugly code should be considered state-of-the-art because it can be written faster. Since we all accept that the pretty code takes longer, it had better pay for itself in some way. The theory is that the pretty code is easier to maintain over time: it takes longer up front, but when it comes time to make a change, since the code is so maintainable, the change is easier to make. Although that makes intuitive sense, I can’t say that that’s been my experience; in 30 years of software development, I’ve never come across code that’s particularly easier to make changes to than any other. I’ve tried to take maintainability into account when I’m writing code myself, and I can’t even think of a time when I had to make a change and found that my foresight saved me time and effort.
It doesn’t help much that none of us agree on what “good quality” code looks like: everybody seems to call all code “bad”. I’ve I’d like to see software development advance a bit in terms of professionalism where we at least agree on the principles of high-quality software code, and the principles are objectively defensible in terms of what the cost/benefits of following them are.
I just started a new big project. Wish me luck. It replaces something held together with duct tape and string concatenation in 5000 line files.
You'll do great!
Web developers constantly need to fulfill new requirements so having a good level of code quality is important.
"Let's rewrite this old messy app in 'modern' technology" - sure it seems cleaner at first, but when you actually reach parity with the old app, you are likely about as messy.
I tend to think that a lot of the messiness in business apps is more due to the complexity of the business rules than the structure of the application.