I don’t entirely subscribe to the first paragraph – I’ve never worked at a place so dear to me that spurred me to spend time thinking about its architecture (beyond the usual rants). Other than that, spot on
I’ll contest their is such a thing as good code. I don’t think experienced devs always do the best job at passing on what works and what doesn’t though. Universities certainly could do more software engineering/architecture.
My personal take is that SRP (the single responsibility principle) is the #1 thing to keep in mind. In my experience DRY (do not repeat yourself) often takes precedence over SRP – IMO because DRY is easy to (mis-)understand – and that ends up making some major messes when good/reasonable code is rewritten into some ultra-compact (typically) inheritance or template-based mess that’s “fewer lines of code, so better.”
I’ve never regretted using composition (and thus having a few extra lines and a little bit more boilerplate) over inheritance. I’ve similarly never regretted breaking down a function into smaller functions (even if it introduces more lines of code). I’ve also never regretted generalizing code that’s actually general (e.g., a sum N elements function is always a sum N elements function).
The most important thing with all of these best practices though is “apply it if it makes sense.” If you’re writing some code and you’ve got a good reason to have a function that does multiple things … just write the function, don’t bend over backwards doing something really weird to “technically” abide by the best practice.
I’ve never regretted using composition (and thus having a few extra lines and a little bit more boilerplate) over inheritance.
I second this. It doesn’t necessarily eliminate bad code, but it certainly makes it more manageable.
Every time inheritance is used, it will almost certainly causes pain points that’s hard to revert. It leads to all these overly abstracted class hierarchies that give OOP a bad rep. And it can be easily avoided in almost all cases.
Just use interfaces if you really need polymorphism. Often you don’t even need polymorphism at all.