I totally agree with you.
My philosophy is, that code should be simple, so anyone can understand it easily and do modifications over the years. We should design our software so that it can keep running and evolve over decades. I have a system still in use almost 22 years now and it's really obvious where the design was good and it was easy to adapt to business changes and what takes too much effort even for very small modifications.
OOP definitely helps, but if you do too much classes and inheritance, you can easily get lost and confused. Some design patterns may help in some ways, but if I have to name one, that I hate - it's dependency injection.
Consider yourself lucky then. In 15 years I haven't seen one application that has held up well for 5 years, let alone 22.
I remember reading an article over 10 years ago on TheDailyWTF, where Alex Papadimoulis was sharing something, that made quite and impression to me - our industry is failing to recognise the failure. We usually declare a project successful when we deliver it and get paid for it. However what we do is similar to building machines. Take cars for example - they are built to last and to be safe. Two aspects we sort of neglect when building software. I know we try, but compared to the car industry - our attempts are such a joke.
What Alex said in the article was, that a project can be considered if people still use it decades later. And if not - we should be rather consider this project a failure, not anything else. Take for example Windows XP - people loved it and used as long as they could. Some systems, written in COBOL, that sadly are still in use are older than me and I don't think anyone can say, that I'm young with all my gray hair. These systems are a massive success.
Note, that I'm not saying, that these systems never got patched or modified in any way. A successful project evolves over the years and people keep maintaining and enhancing it, but in it's roots it's the same project, based on the original design and architecture.
I'm not sure all projects are meant to live a decade or more. I think a better metric is whether you can patch/update a project over its lifetime (whatever that is) without costs skyrocketing or without having that "oh crap, all the original developers have left the team so now no one knows the code anymore!" moments.
Sadly, what I have noticed is that everyone knows how to write good code, everyone can criticize for hours past projects they have worked on on how poorly written the code was, yet when push comes to shove the first thing that get thrown under the bus is quality.
I think this is the article: thedailywtf.com/articles/What_Coul...
I had more or less the same path as yours (Turbo Pascal & Z80 assembler in high school, Java (and some matlab) in University, C++ on my own spare time, now Scala) and I can understand your epiphany.
Design patterns are useful, but in some scenario they're just too overkill :)
Also, if you're going fully functional, I recommend this video
I'm not advocating going fully functional (or fully anything really). All I'm saying is we should look at the requirements first and then try to find a way of getting them implemented with as few abstractions and translation layers as possible.
And thanks for the link, I wasn't familiar with that (the presentation, not GoF, obviously ;) ).
You do have some points. Writing a webapp server in Spring Boot doesn't really feel like using OOP. However, OOP was essential to build the framework to begin with. I like Kotlin; it offers a bunch of functional-style APIs out of the box, but also has the sane OOP principles we know from Java. I also agree that this is not neccessarily a language issue. It is totally possible to write code in Java which isn't object-oriented at all. What makes me feel a little uncomfortable with languages like Go is that they don't support things like inheritance in case that I need them. While I'm still very enthusiastic about OOP, I find myself using a lot of functional APIs too.
Remember, inheritance in Java (and probably other languages) is achieved by composition - hence my perpetual confusion when I see the "composition over inheritance" argument. Just add the "super" structure as a field in your "derived" structure in Go and voila! instant inheritance. Well, not really, but close enough for a lot of cases I imagine.
What can I say, life begins where your comfort zone ends ;)
We're a place where coders share, stay up-to-date and grow their careers.
We strive for transparency and don't collect excess data.