I started writing this as an answer to someone on some discord, but it would not fit the channel topic, but I’d still love to see people’s views on this.
So I’ll quote the comment but just as a primer:
The safest pattern to use is to not use any pattern at all and write the most straight forward code. Apply patterns only when the simplest code is actually causing real problems.
First and foremost: Many paths to hell are paved with design patterns applied willy-nilly. (A funny aside: OO community seems to be more active and organized in describing them (and often not warning strongly enough about dangers of inheritance, the true lord of the pattern rings), which leads to the lower-level, simpler patterns being underrepresented.)
But, the other extreme is not without issues, by far.
I’ve seen too many FastAPI endpoints talking to db like there’s no tomorrow. That is definitely “straight forward” approach but the first problem is already there: it’s pretty much untestable, and soon enough everyone is coupling to random DB columns (and making random assumptions about their content, usually based on “well let’s see who writes what there” analysis) which makes it hard to change without playing a whack-a-bug.
And what? Our initial DB design was not future proof? Tough luck changing it now. So new endpoints will actually be trying to make up for the obsolete schema, using pandas everywhere to do what SQL or some storage layer (perhaps with some unit-of-work pattern) should be doing – and further cementing in the obsolete design. Eventually it’s close to impossible to know who writes/expects what, so now everyone better be defensive, adding even more cruft (and space for bugs).
My point is, I guess, that by the time when there are identifiable “real problems” to be solved by pattern, it’s far too late.
Look, in general, postponing a decision to have more information can be a great strategy. But that depends on the quality of information you get by postponing. If that extra information is going to be just new features you added in the meantime, that is going to be heavily biased by the amount of defensive / making-up-for-bad-db junk that you forced yourself to keep adding. It’s not necessarily going to be easier to see the right pattern.
So the tricky part is, which patterns are actually strong enough yet not necessarily obtrusive, so that you can start applying them early on? That’s a million dollar question.
I don’t think “straight forward” gets you towards answering that question. (Well, to be fair, I’m sure people have made $1M with “straight forward code”, so that’s that, but is that a good bet?)
(By the way, real world actually has a nice pattern specifically for getting out of that hole, and it’s called “your competitor moving faster & being cheaper than you” so in a healthy market the problem should solve itself eventually…)
So what are your ideas? Do you have design patterns / disciplines that you tend to apply generally, with new projects?
I’m not looking for actual patterns (although it’s fine to suggest your favorites, or link to resources), I’m mainly interested in what do people think about patterns in general, and how to apply them during the lifetime of the project.
I think applying design patterns blindly without understanding what problems they’re supposed to solve is often more harmful than not using them. It can lead to difficult to manage code bases because the program is over engineered for problems that don’t exist.
My general rule of thumb is to write code that can be easily adapted to unexpected changes in requirements. Avoid writing code that paints yourself into a corner. Simple solutions are often easier to work with than complex solutions. If what you’re doing adds a lot of complexity, take a step back and seek other options. Maybe you’re overlooking an obviously simple solution to the problem?
I think inheritance almost always has this “painting yourself into the corner” tendency. Once the design is set, it’s often difficult to break free from it. Composition along with interfaces is generally the better choice. Often not even interfaces are needed.
This comes with experience. You learn what works, and what doesn’t. Often you do it the hard way.
Databases are tricky. I have no good advice for that.
I love Christopher Okhravi’s take on inheritance:
Only Use Inheritance If You Want Both of These
spoiler
the “only use inheritance when …” turns out as pretty much “never use inheritance”
I mean, the guy has some good points (and a good microphone and a radio voice) but I don’t think the first video you linked is very well done, especially the intro. Starting with “this is the most important video because it’s going to tell you something that nobody tells you” is a great way to sound …kind of like a narcissistic crackpot.
E.g. the one with 3800 lines is pretty good. (BTW I realized already subbed to his channel earlier…)
Haven’t properly watched the videos, but I don’t think OOP is that bad. I even think encapsulation is one of the core strengths of OOP.
I’ve worked with systems where no thought was put into encapsulation, and those are often incredibly difficult to work with because everything is heavily interconnected. Can’t make a change in a small thing without risking breaking something else at the other side of the program.
I like to see encapsulation as a workspace. It defines the tools we have direct access to. Changing one thing in a workspace shouldn’t affect anything on the other side of the program. Makes it much easier to collaborate in large teams. Minimizes the risk of interfering each other’s work.
So the tricky part is, which patterns are actually strong enough yet not necessarily obtrusive, so that you can start applying them early on? That’s a million dollar question.
That’s what experience is for. Design patterns aren’t rules for a reason - they have a time and a place to apply them.
It’s difficult to say exactly when you should use any given pattern much less a pattern “generally.” Typically it’s when “it’s better to use it then when it’s not” which is very hand-wavy.
It takes experience to be able to say “ahh, what I’m doing here is a good match for a ______ pattern given what I want to accomplish”.
What I will say is that if you’re doing professional development (rather than something experimental or for personal use) then it’s likely you will want to at least consider using a pattern if one exists that matches your use-case. And understand why you do or don’t follow said pattern.