"Program until you NEED abstraction" is on point. I was having this debate at work yesterday, and I said something very similar. Unless you're a savant, who can architect a program entirely in your mind, the most logical thing to do is to write code until you realize that abstraction would be beneficial, implement it, and continue writing code. To do otherwise is just wasting time in my opinion.
Big important problem with this statement: the savant also can't architect it in their mind. Nobody is that good. It's not a little bit out of normal reach to the point where you could get it if you had experience and thought reaaaaally hard about it. Requirements change.
@@zeez7777this is where it starts to crumble. It's tricky though. If you abstract everything beforehand, there is a big tendency to over engineering. If you don't do it there is suddenly no time and money for refactoring, and "it works" so why bother? But it works bad, slow, hard to read and maintain and it's crumbling.
I work in a game engine project which was initially designed with SOLID in mind. At the beginning a lot of those abstractions just seemed unnecessary. However, as the project grows and becomes really big it starts to pay off! It is just a pleasure to work with such a project. For example the basic windowing system was changed two times already from QT to SDL and than to GLFW. Nobody new this would be so at the start. However, as the windowing code is abstracted in a separate static library and communicates with core through small interfaces like IInputObserver which calls abstract functions like OnKeyDown/Up etc. changing the whole windowing system takes a few hours and produces no bugs AT ALL! This is just a simple example. The whole project is like that. A big project created with SOLID in mind is a pure gem!
I think this video is more about asking yourself a question if you need it in your project. Sometimes the answer is yes, sometimes it is no. Sometimes not all of those rules even apply, because OOP is not a magich approach to solve all problems.
yes, solid does work nicely in large and complex project, because with the liskov substitution you can go to a point where you can make most things modular
Indeed! Most people bitching about clean code and SOLID don't know how useful it is in big projects. ESPECIALLY in game dev, with a lot of passionate developers using 90% of his neurons to switch between shader colors instead of coding a good architecture. Code quality in game dev is HORRIBLE. Once you start working with good standards, you'll never want to go back, because instead of feeling pain and agony developing your game, you feel joy. You feel pleasure. You find the bugs, you don't waste 5 hours trying to debug some shit code where you don't understand what is going on.
@@gustavosalmeron2013 You don't understand that codebase, if you take SOLID principals to their logical conclusions, assuming you didn't write everything in that project. SOLID doesn't result in quality codebases, I've been in too many over abstracted messes to count. There is no pleasure, it's pure misery.
@@cyberpunkspike Then you have the other way around. Over engineering is a waste of time just like under engineering makes you waste time. I have seem my fair share during my 5 years in the industry of shitty code. People should use some SOLID as a guideline, at LEAST the Single Responsibility principle. They could at least think before coding, that would be great. I have also seem over engineered code, but at least they are approachable. It is much harder debugging spaghetti code.
This topic always reminds me of the famous Miles Davis quote: "Learn the score, then throw it away" is a quote often attributed to jazz musician Miles Davis. The quote emphasizes the importance of mastering the fundamentals and techniques of one's craft, but then being able to move beyond them and improvise freely.
😮 Great knowledge from my legend that I didn't even know. Thank you! Competent practitioners of most advanced skills tend to realize this one way or another.
My brother in Christ, SOLID is not fundamentals, it's literally the highest level possible trash knowledge that is as far from fundamentals as possible.
This reminds me of the book A Philosophy of Software Design. It states that you should only create an abstraction if it reduces the complexity of the system. It calls abstractions that are a net drain on the simplicity of the system "shallow" and ones that are a net gain as "deep". The book also acknowledges that this means that one piece of code may have to do multiple types of tasks.
@@Dogo.R The books definition of complexity is this. "Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system. Complexity can take many forms. For example, it might be hard to understand how a piece of code works; it might take a lot of effort to implement a small improvement, or it might not be clear which parts of the system must be modified to make the improvement; it might be difficult to fix one bug without introducing another. If a software system is hard to understand and modify, then it is complicated; if it is easy to understand and modify, then it is simple." -John Ousterhout The book starts by introducing this definition and then goes on to describe how to recognize this complexity and how to mitigate it. I've been using its principles in my current project, and I've been really enjoying it. :) I feel like it helps make up for my lack of intuition as a relatively new programmer. I shared the book here because I feel like a lot of what Prime says lines up with the book pretty well. If you like Primes software design philosophy maybe check it out for a more detailed and structured take on things. This is a talk where he introduces his book ua-cam.com/video/bmSAYlu0NcY/v-deo.html
@@monad_tcp Yes, at the end of the day every system you create will have complexity. But you should aim to keep that complexity close to the minimum needed to get the job done. When looking at Primes design principles through this lens you can interpret his idea that you should start with the "concrete" of a solution and discover the abstraction later as an attempt to limit complexity. Similar ideas are explored in the book which spends a lot of time talking about how to spot bad abstractions and how to find good ones. However, there are some significant differences between the books philosophy and what I understand of Primes philosophy. For instance, the book is not as opposed to starting your design process by coming up with an abstraction.
@@rampantporcupineandfriends3793 You start your design process with something you know, which is something Prime seems to be struggling with in general, as seen by his arguments against TDD (assuming that you need to know pretty much the entire project before you can write meaningful tests, which is just an absurd understanding). Sometimes you obviously know that certain abstractions are central to your project.
The whole OOP vs Functional thing just didn't make any sense to me when I first fell down the rabbit hole, but after hearing enough of it I've come to realize it's a false equivalency. Basically it's just click bait to get views on articles and videos. I just write and organize my code for the best balance between readability and optimization, which turns out to just basically be Functional OOP. Although, I do get people's beef with inheritance, and when I first learned OOP I way overused it. Since then, I only use inheritance when it's the best way to solve a problem, which is very rare, but very powerful in those rare situations. Yet it's another thing people want to fight about and claim a position of being anti or pro on... So it's no wonder all the software on my PC and phone are getting worse and worse with every new release.
Thank you! I had to maintain an overused inheritance project in Ruby once, it was horrible. Everything was hidden. I had to read code for hours and hours just to grok anything. Then I got reassigned to another issue that was higher priority in another project. RIP whatever issue that was, it's been a decade and I still remember the frustration.
@@samgraham182 Oh man, that's classic ruby. I don't miss working with it: bloated rails, annoying metaprogramming in the least expected places, "rockstar developer with god complex" coleagues, cultish "the rails way", hours long discussions about code style, etc.To hell with it
I dont really study programming too much (Im more of a make things work kinda guy, trying to change this). Im very much a vibes based programmer. The realizations you have come to are very close to what I have been leaning towards when building things. Locality of behavior is really important for me. The only reasons I break it is if the same logic is needed elsewhere and should be aligned. When I have to read 4 different files to figure out what one functions does I get very angry and want throw laptop.
when people do the "functional vs oop" thing, they are raising the problems with oop, then trying to come up with something to contrast it with. but structured programming wasn't en vogue anymore when these comparisons first cropped up, and functional programming seemed like the new hotness (despite being invented some time in the 50s through 70s). so fp got saddled with oop's baggage, being nominated as the next big island of thought to jump to. turns out neither is a good dogma, and they're just different ways to add abstraction to a program. abstraction can be valuable, but it comes with a set of ongoing costs. it should be deployed in hesitation, when it produces significant value in return, when you have a provable, reasonably sizable, and current burden that it resolves.
I honestly don't believe that you only use inheritance very rarely. Every time you use an interface/abstract class, you are using inheritance. The Strategy pattern is one of the most useful applications of inheritance and it's rarely not useful at all. It enables you to swap out implementations in parts of your code that don't ripple through other areas and that alone makes it such a wonderful tool for long term maintenance.
Liskov doesn‘t say that you want to have a lot of subclasses. It just says that if you write a subclass, you better don‘t do unexpected things there that breaks code which codes against the base class. If you never write any subclass, you trivially follow the principle.
I think a big ding against SOLID is that theyre called "principles". Theres only one principle, and it is "manage complexity". Maybe these should be called "perspectives" (the name Lens or Optic already got stolen by the FP folks) And the perspectives are best understood by the outcome of violating them: - SRP: no god classes - O/C: avoid fragile base classes - Liskov: dont have your subtype do something completely different - ISP: no thousand method interfaces - DI: dont couple to dependencies. Ive seen time and again that avoiding this (decent, vague) advice, the worse and more convoluted the code gets.
That's very helpful, actually. The standard pithy description of these has been really hard to wrap my head around, especially Liskov. And at least for that one, I think I know why: that sounds like the most bone-headed thing ever and I'm pretty sure I've never tried doing anything like that. It's a long-standing problem with OOP in general for me though-at least in the old days (90s/00s) when I was learning this stuff they _loved_ their jargon rather than speaking in plain english which made everything 10x harder than it needed to be.
I find it so annoying to have the question "hm, database isn't saving correctly, let me see what happened" and then I need to open PersistenceManager, DatabasePersistence, InvoicePersistence, and Invoice before I can start debugging. Thanks SOLID, very understandable! Worst part is you'll go right to Invoice and wonder where the database saving is, and so you need to search for Invoice and then you find InvoicePersistence and then you realize that there's no logic, it's an interface. Now you need to search for InvoicePersistence to find where it's implemented, then you find DatabasePersistence. And then turns out the bug isn't there, it's actually in some random file that calls invoicePersistence.save() and it turns out someone passed a FilePersistence to the wrong method somewhere else and so DatabasePersistence.save() was never actually getting called in the first place.
Yeah but you need to first ask yourself why are you doing any of that. It isn't anything to do with SOLID. You have separated your code base into small easy to test units and then didn't test any of them. If you have to instantiate the entire system to figure out where a bug is occurring then something has gone seriously wrong earlier in development.
@@deloftie3619 You say that like `saveInvoice(invoice, database)` would be difficult to test. OOP tends to be hostile to tests in my experience, because intractably bundling data and functions makes it very difficult to validate that the right side effects happened to the right objects at the right time. That's not me arguing for FP, but a strict separation of data and logic is literally always healthy. I also, and I say this as a huge fan of testing, warn that your test suite will entrench any structural status quo that you test. This means that structural methodologies that cause large sprawling implementations (like OOP) will become calcified by your test suite. If you do try to refactor those abstractions later to be less cumbersome, you'll be glad the tests are keeping you safe, but they'll also double the effort of the project, and that's the trade-off.
@@deloftie3619 In my experience, the nasty bugs are between layers (of abstraction). Unit tests are fine, but automated integration tests that cover multiple layers (e.g. from UI to database and back) can be gold.
@@MaddieM4 "That's not me arguing for FP, but a strict separation of data and logic is literally always healthy. " The problem with that is that in large systems the code becomes tightly coupled data pipelines that are very hard to change. Imagine it like stringing together a whole load of Linux commands with pipes and then deciding a few years into production that actually the data format between two of the pipes needs to change. In OOP you are trying to, as much as possible, get rid of "data" as a separate concept to "behaviour". This obviously can be hard to achieve, particularly if engineers are used to thinking of systems as data pipelines with procedural functions manipulating a piece of data and then passing it on. But if you can crack it you find you can open up your system to be much easier to change and evolve. And all the SOLID principles are doing are showing how that can be achieved.
SOLID is an ideal. Experienced programmers who understand its concepts write SOLID code, but break the rules when they're not worth following. Teaching that SOLID is bad is just as harmful.
SOLID can be applied to every level of your program. You don't want to follow the rules 100% in every part of your code. And what a lot of people don't understand, is that all the 5 principles are actually the same principle.
ONLY if you teach people that these ideals must not be followed unthinkingly, and provide hard examples of when the prudent action is to not follow them... then I'll get on board.
@@gageduke7652 no, MVC is just one out of many design patterns that exist in Software Architecture. I think design patterns are more useful to think in.
Prime is so correct at the end there. (Almost) Always write out concrete versions of your classes, then go back and abstract them. It took me 10 years to learn this (I am a slow learner). This way you know what parts are repeated a lot, you can then tease them out and abstract them as needed. Your overall structure will make more sense from the first abstraction attempt, instead of doing it as you go along, where you can't predict your ass from your elbow.
@@sasieightynine I didn't read it anywhere unfortunately. It is just a pattern that I learned to recognize after making the same mistakes over many years. I suppose an easy way of putting it is something like this: Start by writing your program in the dumbest, most straight-forward way possible. Iterate upon this and try to tease out any repeated parts and abstract them, but only abstract them if you NEED to. Don't just abstract things for 'the future', it doesn't exist. You are in for a world of pain if you go down this path. Instead, focus on the problem at hand, and possibly the most predictable outcomes if you have the resources to spare.
A lot of these seem to convey an emotion of "if you ever have to refactor this, then you made a mistake. You should predict and/or accommodate the future perfectly" I'd much rather see principles around the idea that "you cannot avoid refactoring, the only question is how to make it as painless (or dare I say easy) as possible" and a lot of that difference is diametrically opposed when it comes to the guidance on inheritance
All of the SOLID principles are to facilitate easy and painless refactoring. Most modern devs don't care that much about maintenance and refactoring though (they just made a pile of mud and then move project and/or job and start again) which is why you get videos like this that are saying don't bother with SOLID principles.
@@deloftie3619 In my experience solid principles make refactoring harder because more often than not you build the wrong abstraction, resulting in first having to undo the abstraction and then building the right one. In a perfect world where we can perfectly predict the future solid makes sense. Reality is very different though.
@@Chisegh I don't follow what you mean by "undo" the abstraction. If you follow SOLID principles you never need to change your existing code as your product changes or matures. You are only ever adding new code It is the exact opposite of having to predict the future, which is what you have to do with procedural code because you have to either know that function X does everything it will ever do or you have to add new functionality to the existing function X as you develop. This is because functions are tightly bound (it is difficult to say to your exist code "Stop using this function, use this one instead") so existing functions are constantly changing and growing, which massively increase the complexity of the functions and the risk that bugs will be introduced. I've worked on so many software products where after a few years every function is a collection of if statements for all the new functionality that has been piled upon the function over the years
@@deloftie3619 to put it simply, I disagree. "it is difficult to say to your code, stop using this function, use this one instead" I only find that to be difficult when the functions entail large amounts of state, which is you guessed it, from OOP The worst nightmare refactors I've ever seen have all involved unnecessary class hierarchies and the inability to fix problems without causing problems in new places, or ripping everything inside out to procedural again. When procedural code just does what it says right in front of you, instead of reaching through so many abstractions, it is easy to refactor by comparison. Especially with a dash of functional programming to minimize use of state overall.
@@AndrewBrownK "I only find that to be difficult when the functions entail large amounts of state, which is you guessed it, from OOP" Well its not state I'm worried about in this situation, its binding. If you have a function that is called in a 100 places in your code and then you need to alter how some of those places behave you have a tough time about it. I've seen so many large functions with multiple paths that have been added over the years to account for new functionality that has had to be brute forced into the existing function. And don't get me started about having to slowly increase the parameter size of a function as the function becomes responsible for more and more. "involved unnecessary class hierarchies" Well sure, the "I" in SOLID is essentially don't have class hierarchies that can break your code. "When procedural code just does what it says right in front of you" It does, and it is perfectly fine for small projects that won't change much. I've nothing against procedural code, I write C and procedural Python all the time. But it is a nightmare to have to maintain procedural code in any large system (again binding) that grows and matures as the business requirements evolve. Which is exactly what OOP was invented for.
locality of behaviour is a thing - if you have to reach outside to a wider radius from where your working then your cognitive load shoots out the park - so to speak - which seems counter intuitive of the principle that we want our code to be easier to read and understand
This is the problem that we are stuck in this cycle of someone writes poor code and then someone else has to debug that code and find out why it isn't working. So everyone is constantly trying to understand what the hell the other person wrote. At this level having a procedural style "everything is happening in this one function" approach makes this easy. The problem with that is that you end up with a God awful mess at any abstraction higher than the specific bug or issue the programmer is trying to fix. These apps become balls of mud, where fixing specific problems is trivial but any larger change becomes almost impossible. So the cycle becomes an app that it is easy to fix what your buddy wrote a day ago but you have to throught he whole app out every few years and start again. Developers are so used to this cycle now that they see anything else as an impossible utopia
It's almost like people who created SOLID are in the business of consultancy aka never actually solving any problems because they need a next paycheck. Imperative programming is very easy and straightforward, once you know your problem and your program, you map one on the another. OOP is a cult where to solve a problem you need to travel to a shrine, pay a high priest a bribe, burn incence to the gods, hope for ten years and then interpret random events like proof it did actually work. Guess which one corporate parasites prefer.
Generally Liskov principle states "dont let the tail wag the dog", so hierarchy of classes should be in sane composition with each other. Rust traits are the good example of following the principle
I think `ThePrimeTime` should actually like the Barbara Liskov principle, as it WARNS YOU, that if you want to use objects and interfaces you need to obey the substitution rule. With such a warning, it is clear that the bigger inheritance hierarchy, the more places that you need to obey the rule, the harder it is to write new code, modify existing.
My gut feeling is have a tendency to under abstract because abstracting from a concrete state is easier than to make concrete from an abstracted state. It almost always is a one way road.
In my experiences, good abstractions are easy to miss and non-obvious at first. Whenever you are adding or changing something in a class or function that requires another class or function, ask yourself whether you really need that other class or function or if you need *everything* in that other class or function. If you answer to the last part is "no", then there may be an abstraction waiting to be implemented. Same would apply to unit tests. Try not to introduce other components that you do not intend to test in that instant. If you can't, then you need an abstraction.
Fully agree to KISS. For me YAGNI is important too. But DRY is a difficult one. I generally won't repeat myself implementing the same logic in different places for the same types. But e.g. having the same string at different places doesn't bother me.
@@cod3r1337 for sure! I try to concentrate on KISS and YAGNI for my projects as well in my day job and private projects. And, what I in addition to that absolutely hate is when people are writing code in different languages in exactly the same way. E.g. writing a microservice in Go with hundrets of dependencies as they would in Node or Java. Go is all about KISS and that is absolutely ridiculous!
Now THAT was SOLID video. Thanks Primeagen, I've been coding for about 10 years now, have read about SOLID before, but I've never fully agreed to all principles, I like going with the flow, just like you, start with concrete and hands dirty right on, then I take a sense on what actually needs to be abstracted and move on, thing is I thought I was a poorer programmer cause I don't fully agree with SOLID, this video gave me a lot more confidence as I honestly agree with practically all the arguments you brought. Being a good programmer is finding a balance in between strong principles and compromises, for the overall benefit around the application being built, our mental health, our future-self, project turn-over, quality, maintainability... a balance in speed and quality.
All this discusions is like discusion: Should you use tea spoon or not? But this question don't talk about time and place of using it. Is it about eating steak? Is it about eating soup? Is it about drinking tea? The answer will be diffirent in all three situations. You should not even try to use tea spoon to eat steak. It may be possible to use tea spoon to eat soup if you don't have any better option. You should use tea spoon for drinking tea (if you need to put something to tea). You should not use fork to drink tea. You just need to find right time and place to use it. But depending at your situation you may not use it in your life time.
The comment about knowing when to abstract and when not to abstract really resonated with me. My sense is that the SOLID principles become more valuable as your app gets larger and more complex. When you are first writing a small app, many of the principles cause more problems than they solve. When it is easy to execute on them, great, do it. When it is awkward or difficult to do it, wait until you actually have a need to actually do them. Many times, the best choice is to just be mindful about how you can avoid closing doors on your design unintentionally.
I don't see anything wrong with SOLID principles. Single-responsibility: fine but quite vague. Open-closed: good. Liskov substitution: very important. Interface segregation: useful. Dependency inversion: very useful. I would definitely be a worse programmer if I didn't know about Liskov substitution principle or if I didn't know how to make use of dependency inversion.
Regarding pre-emptive abstraction: You Ain't Gonna Need It ...until you do... But most people only get ~5% of such predictions right--which leaves ~95% useless, brittle, noisy wasted-effort if allowed into the codebase. As Prime said, by the time you have proof that the abstraction is needed, it's easy to extract out exactly what's needed from your initial concrete implementation.
I think those numbers are not really reflecting reality. Or maybe they do and I'm just biased. Depending on the domain and personal experience level, you can more or less safely predict future requirement changes and plan where abstractions might be needed and where not; and if you needed them in hindsight, you are glad that you prepared for it when there was plenty of time.
@@Asto508 The percentage definitely goes up with experience but it's also definitely low for juniors. One of the most significant ways seniors increase their odds is by simply making a lot fewer baseless assumptions/predictions. Software engineering is fairly easy to change on short notice, especially if you're familiar with e.g., JetBrains' refactoring tools and also if the code sticks to what's actually needed / in-use. What's hard to change on short notice is architecture/infrastructure so it is good to get that as right as you can up front. Still technology is complex enough that the first #83 features of a platform will align really closely with your requirements and then six weeks in you try feature #84 and find you got bit in the @$$ by it's marketing. Then you transition onto platform plan B because the large gap it has on feature #6 is actually easier to close even after re-implementing 🙄.
@@Asto508 it also heavily depends on whether the code is contained or spread through the code base like cancer, and on the flip side (somewhat) if there is some "god class" thing going on
@@Asto508 I agree, but your abstraction may be the interface of the concrete implementation too. As long as there is a single piece of code handling the functionality, it is simple enough to extract a polymorphic abstraction later on.
I think ThePrimagen should push his new paradigm: SOLIDR, single responsibility, etc, etc, etc and then R for "If all else fails just RAW DAWG IT IN THERE WHEREVER YOU WANT"
"single responsibility principle" doesn't mean the class should only do one thing (although uncle Bob argues for that too), it means it should only be beholden to a single stakeholder, i.e. it should only have a single reason to change. but you're right in that this isn't the law, if it makes a small app way more complicated for the sake of adherence to SOLID, then it becomes counterproductive. Liskov is for Barbara Liskov :)
Exactly. The architecture of your software should mirror the business units of your enterprise depending on it, or the diversity of your stakeholders in general.
Hi Prime, generally I like your take on programming, but on this one, I kind of disagree with some of your points. The Single Responsibility Principle might seem like overkill for a small project and I agree with your stance, but as the project grows and its components become critical and sensitive, separating concerns becomes more valuable. This helps to prevent one part from crashing the entire system. For example, if the rendering layer crashes because of issues with the cache, it doesn't make any sense. The Liskov Substitution Principle isn't about advocating for inheritance per se, but rather highlighting some of its challenges. Classes should be inherited based on their behavior rather than just their properties. If class A inherits from class B, we should be able to use B wherever A is used. In this regard, using composition instead of inheritance makes sense. Using an interface or trait enables us to define a contract for class behaviors, so if A and B used interface/trait, they can be exchange As for the Open-Closed Principle, I don’t think there can be a valid argument against it. As you are saying everyone uses the strategy pattern, everyone uses the Open-Closed Principle. "Closed" refers to consuming an API, while "open" pertains to building this API. When we say "closed," we imply that the main logic of our application must be “stable” and our main free from considering implementation details. For example, when I use a library, an API or even a (high-level) language, I focus on the algorithm; I write it, my logic is correct, so my code is completed. If there's a bug or a need to optimize the tool, change the database, or tweak something under the hood, it's not my concern-it's implementation details. And if there’s a new feature landing on the API, it’s good. But the main logic of my application shouldn't change because of such changes. My general stance in programming is just don't over-abstract, don't jump the shark. There're good patterns and good practice but not always necessary.
Inheritance is just one way to implement polymorphism. If you need polymorphism, then the complexity will accrue somewhere, in the class hierarchy, a switch statement, or elsewhere, you can't really avoid it.
Polymorphism is so niche technique, you may not have to use it even once in a lifetime. And this applies to all SOLID principles, but people can't just sit straight and do nothing 5 minutes...
In my experience there are VERY few hard rules or principles one should follow, but a lot more guidelines. When I first started programing my OOP inheritance tree would make the Habsburgs blush. And then during the functional renaissance I tried to use it everywhere and it didn't work out. because a functional approach didn't fit the problems. Remember paradigms serve us to solve problems we don't serve paradigms, use the right tool for right problem and don't be dogmatic. Some problems are easily solved by inheritance, or composition/interfaces, or a functional approach. And experience and your personal discretion will help you make that right choice.
Because all of them are solutions but also constraints on otherwise possibleto calculate code. Same as languages, ideal one would allow me to do anything, best up to declaring new keywords andsemantics if I need to while preventing me from usinng that on accident.
The problem with SOLID is that one needs a lot of experience to understand and interpet it in a way that's actually useful, but if you have this experience you don't really need SOLID.
This is not a problem. This is true everywhere in software. Experience is key to everything. Principles written in books are worthless without it. That's why in his Book "Clean code". Robert C. Martin goes into extreme lengths to explain that we need to think hard about and experiment long with the SOLID principles he is proposing. Nowadays, they are just catchphrases used in interviews or in Pull Requests to make an argument...
I kind of agree to a certain degree. Single responsibility and dependency inversion are pretty easy to understand and don't require much experience to benefit from.
@@damdoumibou3ajaja338 Robert C. Martin recommends principles that can not be implemented well regardless how much experience you have. 12 lines per function is just the wrong foundation no matter how you apply it.
I think of it as "SOLID adds no marginal value over what you already should learn". For example, that part where the article says "logic and a database in the same class violates SRP". Well, basic OOP already says to de-couple by slapping an abstract interface over the DB (if the project is large, mostly in the final state, and expects to swap out different databases). Thinking of that as fixing an SRP violation adds nothing useful. Likewise, learning about public-interface/private-implementation is worse if it starts with "here's SRP, now we're going a learn a technique to avoid it".
30:40 this!! it’s the spidey sense that you might want to extend later, but enough experience to hold off. you don’t make it impossible to extend, but you also don’t try to make it extendable in every possible dimension
the problem is not solid, the problem are programmers who don't know how to produce code, that is in anyway extensible, modular or (i hate to say it) maintainable. that's why SOLID is needed to teach those idiots what not to do, if they get it, they are allowed to break SOLID principles, and make their own decisions, until then I'll keep treating them as little children. I've seen so much BS out there in the wild that I have PTSD. - handwritten, xml parsers, - functions with 3k+ lines to generate templates for templating engines, - 300 lines of imports in java class files, - functions with 17 boolean parameters, - testcode in production, - orm.execute("select * from xxxxx").isEmpty(), - 10 levels of class hierarchy - formatting/display logic in sql statements - true means true, false means true, null means false as long as simple concepts like inversion of control, dependency injection, function composition, separation of concerns are not known, we as an industry have to teach SOLID.
I feel that the real problem is the appropriation of existing connotation-laden english words like clean, or agile, or solid. If he called it Robertist Code instead of Clean Code, or Martinist Manifesto instead of Agile Manifesto, or UncleBobist Principle for solid principle, I would have nothing against it. Just imagine how creepy it would be if religions were named using existing everyday words - say if we had to use the word "genuine" for "Christian" or "honest" for Hindu and so on.
People criticize OOP because it hides details so you need to switch to a different file to find those. Yes, and that is a good thing! When you read a program you should be able to understand it’s domain logic - the purpose of your program without knowing which JSON format files are written och read from or if the server runs HTTP/2 or HTTP/3. That being said I don’t like inheritance , classes should only implement interfaces, not extend abstract baseclasses. Functional programming can help you avoid a lot of extra classes, so it is good to mix it in.
If you ever worked with the Linux kernel that is purely written in C and scattered around thousands of files, you would instantly realize why it makes zero sense to equate OOP with having code in multiple files.
@@Asto508Sorry, I think I was misunderstood. I did not equate OOP with using many files, you can put everything in one file if you want, but it turns into a mess. Second, the Linux kernel IS written in an object oriented style, emulating classes with macros, structs and function pointers.
My first rule of programming is write reusable code. Reusable code makes it easy to not repeat yourself, it is easy to "reuse" in tests to verify it, it is easy to reuse existing code when refactoring into new abstractions. After finishing a function or class if you have a hard time imagining that thing being used outside of the specific use case it was written for then you should rethink the design.
Totally agree to all the statements in this video! Before you start programming you have to think about what you want to deliver. Do you want to build code that is very flexible and general because it will be part of a project that changes the next 10 years or do you want to deliver specific code for a specific problem that will not change. I almost always choose the last solution which means less code, less (abstract) thinking and less effort regarding time spent which means being very efficient and very cost effective. And this year my new friend ChatGPT solved some annoying or complex coding problems for me (but you have to know how to use this tool) so this makes it even more efficient.
the last year I've been dealing with a project of like 16 microservices in typescript, all using really dense solid patterns... every opportunity to use an interface or a factory was taken, even if there's just a couple entities involved. Want to add a new database table? get ready to write 7 code files to follow the pattern. want to find a bug? get ready to drill down through layer after layer of abstraction. I mean, the code can be scaled out into infinity and beyond, with multiple teams working on it happily... but its a 300 user internal app with a pretty limited scope, and like one or two developers. I've really come to absolutely worship simplicity.
In my career, I've far more often encountered code that was under-designed than over-designed. While what you describe sounds crazy, try to be somewhat accepting of it. At least the developers tried to segment and structure their programs. We all get things wrong, but appreciate the attempt. I've worked in so many code bases where the developers didn't care at all about design and just wanted to count story points as done.
Your example sounds like the opposite of SOLID. If you have to change 7 code files or mess with multiple layers of abstractions at once just to implement or fix something, then you failed in creating good abstractions while violating the Single Responsibility Principle.
Abstractions like the ones SOLID calls for make you *feel* like you’re making things easier and more maintainable. Really, you’re just hiding the actual function of your code underneath a coat of pretty paint.
I feel like this is very true for single responsibility. One thing that is rarely noted is that separation of concerns is mutually exclusive with locality of behavior. So you look at a small class that does only one thing and think "oh, this is simple to understand." But then you start actually working with the codebase and realize that a single process will span several files and contain several layers of indirection that make it very hard to track the actual control flow (made even worse if you add inheritance). Conversely, procedural code with high locality of behavior often looks ugly and disorganized, but when working with it you realize its very easy to read and understand all the code paths. There are tradeoffs with both, but I personally consider locality of behavior to be the most important, and only move away from that of testability becomes an issue.
That's only true if you're doing it wrong. There is overengineering and pointless abstraction, but there is also the opposite that is equally bad. If an abstraction is not serving a real (foreseeable) purpose, then it's just a useless layer and simply a wrong application of what abstraction is supposed to solve. The hammer isn't bad because you try to drive screws with it tbh.
@@LusidDreaming complex control flow often indicates poor design; even with multiple modules participating in the control flow, the flow should be (mostly) one way, and this is makes things relatively simple regardless of how many modules there. That said, there is generally no reason to split things into multiple files until those things are actually needed from multiple places, perhaps not even then.
Ive always felt bad that i typically ‘script’ much in the way that i see you’ve done here taking the tradeoff for ease of writing vs strictly adhering to these principles, glad to see I’m not the only one! #gatekeepingprogramming
So essentially you use all these princuples but you don't apply to cases where it doesn't make sense, like everybody else does. OMG, what a discovery! Everybody knows you shouldn't apply a tool where it doesn't make sense and that's why it's called "principle" not "rule you apply to 100% of cases". Sensationalised title just for views and clicks that's supporting SOLID with the title "Solid Programming - No Thanks". These kind of posts hurt the industry and creates polarisation in regards to engineering principles. You're turning this into politics for personal reasons, what a shame.
Solid is great, but not when you are dogmatic about it. Always think about YAGNI and KISS whenever you want to abstract. Good devs know solid rules, the better devs also know when to break them.
I teach it like this. Writing software is a lot like music, we learn foundations and guiding principles, structure and theory. Once you developed mastery in those areas, go ahead and break the rules. That's where the magic happens. But I'll be easier to ease back into the foundations if things go sideways than it will be to establish foundations when you started out without the rules in mind. Much like art, negative space is also important. It's ok to leave things out. You don't have to be comprehensive if code is going to be unused. Worse yet though is someone who refuses to know the rules. They can be dangerous contributors and toxic collaborators
ive seen people take Single responsibility too far in our codebases. for example. there was this part of the project that would transform a grid of points. ie rotation and translate. these are all simple concepts that can be done in a few lines. whoever originally wrote it did every math operation in its own function. every function was a single line. the problem with the idea of single responsibility is the line is ambiguous. because, technically every function had a single responsibility of a single math operation, and doing this made the code much less readable, who could have guessed. end of the day the programmer still has to understand where to draw that line, where abstraction is a benefit and where it is not. in some ways these principles we have do end up feeling like they say a lot of nothing as there is no one size fits all and you end up losing the nuance to the when and why
Thats not the SRP concept, youre wrong, it's not about to split every task in functions, it's about every function only would have a reason to change, no many reasons.
Seems totally fine for your grid math lib to be split in multiple functions though? Unless you meant in the sense that you had a specific set of operations to perform in for the final output nad they split that pipeline up for no reason? IF that's the case then yeah that's wack as hell, that's why I tend to describe my functions in terms of "data op" "pipeline" and "transform" the math operations (rotate, translate, etc) would be a transform and seperate but the pipeline for the final output would be in its own thing (func calc(in){in.rotate(1, 2).translate(3, 4).rotate(5,6)}) and the data op functions would befor getting the in object from a data source and another for sending that off somewhere
SRP is not separation of concerns. They are different things but are confused all the time. Everyone thinks they know what SRP is but no one really does. It's an organizational principle, not directly a code related principle. The way I understand it the best is that you don't want multiple stakeholders requesting things in a single software entity (i.e. but not necessarily a class), as they are likely going to request things that conflict with each other.
@@TurtleKwitty yeah, it was basically taking one 2d coordinate space and mapping it to another. just a linear transform and a rotate i was tasked to convert it from only being used to converted between two hardcoded coordinate spaces to be user controlled at runtime. it did the latter, there were like 16 functions responsible to convert between the two. so it was like func convertToLocationX(x){return x + LOCATION_X_ORIGIN} and below it func convertToLocationY(y){return y + LOCATION_Y_ORIGIN} rinse and repeat
Makes sense if each of those function are generic and reusable. However, If those functions were built only to serve another function or to be used to execute a single task once... yeah that's weird.
Appreciate the solid reflections on this topic. Hard to get inputs from practitioners sharing examples of how to effectively apply these principles in the real world
2:06 This is the main point. Using SOLID principles is great and all but if you’re implementing abstraction after abstraction where the readability, testability, and maintainability suffers, it means you need to scale back and refactor. But more often then not SOLID principles can help you achieve those goals but like many other aspects, developers can take things too far. It’s the old adage “if all you have is a hammer, everything looks like a nail” (and that’s how I feel about functional programming also)
17:17 “you abstract when you realize the abstraction” Love this principle. I’m building some Rust code to translate data between two programs, including a common format I made to make processing easier. During that process and discovery of how the two applications have their data formatted, I’ve discovered ways I can abstract and make common functionality between the two. I didn’t start with that, but discovered it as I realized two things worked similarly. Also the abstractions under Single Responsibility are totally not needed and the exact reason why I hate Java. I shouldn’t need 5 classes to read a text file
Here's my design principles: Make it as simple as possible without compromising function. All other design principles are sub to this, and are used when they support simplicity in a given context.
If you have 2 pieces of related code with very similar functionality, which is the simplest: to use shared data+code for the similarities, or to implement them separately?
Liskov principle just means that if you have, for example, an abstract base class (MeleeWeapon) that implements an interface(IMeeleeWeapon), you can have several concrete classes (LongSword, Axe, MorningStar) and, since you've coded to interface, any one can be dynamically selected at runtime and your code will handle that particular instance object since it WILL BE a Weapon/IWeapon. Beats the hell out of 800 lines of nested if/else statements.
Liskov's is a bit more involved than that (and applies to all polymorphism): e.g. if the client code expects the server code to always create a specific file on the filesystem, then that must happen in all sub classes
Completely get rid of the abstract base class: what matters is that the interface the concrete classes implement comes with a set of constraints that the implementations of that interface must adhere to. Implementations that stick to those constraints are well behaved and allow code that uses that code to make certain reasonable assumptions. If your implementations break that contract, then they are not substitutable with implementations that adhere to it. That's LSP in a nutshell.
About SOLID programming being good but preferring to be loose and not a requirement. I think this also applies to programming paradigms in general. Yea, you can program every single thing strictly following OOP. Or you can realize, this little thing is only in used by this 1 class and can be a single function. This little task doesn't even need its own function. Paradigms and these approaches to organizing code or guidelines, they're all tools. A good programming knows which tool to use for a particular task. As a general rule of thumb, yea single responsibility is a good idea. On the job, you're balancing readability, time to write & design, effectiveness, maintainability, performance and whatever other things I missed.
OOP is pretty bad when you build something under the philosophy of "I want this behavior to be standard everywhere" but then it turns out that behavior varies slightly and need independent implementations that you're trying to wildly tie together in dumb, complex ways. Procedural is bad when you say "I'm just going to build what needs to be done" and then you end up with a system where changing one flag means you need to track down 30 different places in code to add the same if statement in every independent implementation and everything burns down if you miss one. As prime says, it's all intuition, you're going to need to be able to make reasonable assumptions about how a particular problem is likely to evolve as it grows, and use the correct approach accordingly.
Your first sentence is literally what OOP is not about. One thing that OOP tries to solve in an elegant way is the need for polymorphism, so exactly that you need partial different behavior within an otherwise same-behaving object. It's exactly one of the strengths of inheritance to allow specializations that stray away from the generic inherited parts. It's the whole point to allow easy modification for "non-standard behavior" instead of creating a whole replica that only differs on a miniscule level to its parent.
"track down 30 different places in code" -- lol, and you wish it's just in code, and not in DB schemas, logs, maintenance scripts running on some forgotten raspberry shoved in between drywalls, frontend (JS *and* CSS). say, adding a message severity level seems trivial but it can become a *real big adventure*[tm] real fast. or worse -- slow, with bonus side quests such as angry customers or dozens of people standing around awkwardly in warehouses being paid for doing nothing because what? you did *not* know that `danger` is a valid severity? you were naiive to think that it looks like enum, quacks like enum, but no oh boy this string is free like the bird and yes the whole frontend went down because, and only because you did not fall back on unknown string when f**ing switching style=`color: ...` in Vue? (i know, weirdly specific -- it did not happen in production to me but it drives me up the wall how easy it is to make exactly that mistake, when working on "hey do not over-engineer me" kind of codebase)
@@Asto508 aside, but I recommend Christopher Okhravi's last video on this topic, "Only Use Inheritance If You Want Both of These". i love his channel, especially the last "wave" -- he came back after a long hiatus and now he's just killing it with much smaller and focused videos
Totally agree with you, I like my program with very minimal classes, otherwise, it gets confusing really fast. I can have classes (as I make the embedded programs most of the time) LCD_Display class, Communication, GPIO, sensors, that is it, I would not add extra things to make it more confusing.
I think the sad thing is a lot of the time that "amateur" 300 line function we wrote when we started is where we come back to when we realise having 1000 lego blocks all over the floor is not nicer than having one lego millenium falcon. Your lego blocks are the primatives of the programming language itself, the for loops and the structs and whatever. You don't need to make your own weird duplo blocks.
Nah, definitely should have a separate InvoicePrinter class to handle printing 🙂 In my real world enterprise, SOLID applies very much, especially open/close. Mainly because we are at a point where we can build solutions that leverage utility computing services, so decoupling code for quick and easy extension of a persistence manager is very useful, for example, we might use an AWS implementation of a service today, but next month we want to switch to the GCP implementation, in large enterprise where we're continually looking for cost efficiency, we would do this kind of thing quite often. When you're a long term employee of an organisation and you know how the organisation works, proactive abstraction can be easier and saves time.
I failed at so many tech interviews because these stupid questions about OOP, Clean Code, SOLID, or Scrum keep coming up. And as soon as I explained to them categorically why these things suck ass (with concrete examples) they immediately become hostile.
I can understand Scrum but why the others? Actually, I am curious because whenever I am like, we should do SOLID, there is push back, "we need our code done yesterday." Which it could be but any modification would take forever. SOLID has consistently made me the least sad when I need to update code, if I need to update code at all. Best case, I am simply changing out the implementation and moving on. It is funny how people use and love frameworks but decry using the same principles and practices that make the framework so great. Golang will have you doing SOLID without even trying. That is what is beautiful about the language. I learned more about how to do OOP right from Golang than even OOP languages like Java or PHP. If you want a bad time, you will NOT be following SOLID principles in Go. Provided Go is best when you are doing a more Functional style but hey, you already have objects just out there why not add some function receivers and add some functionality to those objects? Or don't. Possibly Swift is on the same level of extensibility.
This is the classic YouArentGoingToNeedIt argument of Extreme Programing. I used to spout the "gospel" of XP early on until I understood it's major flaw - the refactoring nightmare. A collection of DoTheSimplestThing turns into an unreadable and unmanageable mess so slowly, you often don't see it until the fixes are enormous. The hidden part of "DTST" is that you have to refactor as you go, and knowing when to do this isn't trivial. This is the hard part. SOLID it kind of the opposite. The SOLID approach requires a fantastic base to even work. There are few people that can design a non-trivial API/Library/OO base that's flexible, easy to extend, and doesn't need to be changed. This is the major flaw of the paradigm; bad choices early on tend to become permanent.
Half through the video. My observations are: in case you are a single person who works on the project, then yes, no need to care so much. But if you are a team (which is preconditions for article) you should definitely stick to S principle. Otherwise it will bite your ace. It will lead to bloating code or huge duplication. Ofc, covering S module with tests is highly needed or you won't be able to understand how your single unit, which is used across multiple places will change the behaviour.
Once again, I create tests when I could not make it in a first try is kinda strong when you are a single person working on the project, but a bit silly when there are several people included. What if THEY would not be able to make some function in a first try?
Having been forced to follow solid I can say the "single principle" just leads to a lot of middle man classes that's sole purpose is just to be the bridge to how to classes interact. Sometimes you can get away with just using a event/messaging system but when the coupling is tight then the easiest and simplest solution is just passing in a pointer. At least in C++. But when you are forced to follow solid that is frowned upon and you have to write classes with long ass weird names that only confuses everyone after a month of not touching it.
the only time i ever use inheritance is encapsulating logging into errors and creating a typed instance of a generic typed interface, like something becomes somethingInt : something
I want all the SOLID advocates to make a game. Even something "basic" like Tetris or Arkanoid. And then still advocate for their one size fits all solution.
For every rule in programming there are exceptions. I prefer to look at writing code as just a selection of rules of thumb, where there are preferred ways to do it and non-preferred ways that are ranked in order of most to least appropriate. But sometimes you have to select the thing at the bottom of the list because that's the only way you can reasonably implement it. Sometimes a function HAS to do more than 1 thing for efficiency reasons because otherwise you'd be looping through the same data multiple times. Sometimes a function HAS to return more than 1 value for that same reason. As long as you understand the code this is perfectly valid.
@@kaanozk the only things that truly matter are correctness and performance. Source code file size, line length, formatting style, naming conventions, build system, number unit tests, all don’t matter.
@youtubeenjoyer1743 hmm ok, I personally would say "what ever gets the job done" and optimize from this point onward. Doesn't seem to be far off from your idea.
Here's my 2 cents on the "S" in "SOLID": Single Responsibility Principle (SRP). I believe (based on my experience) that SRP does a very wonderful and perfect handshake with High Locality of Behavior (LoB). Now, most people like to define LoB as co-locating all the logic in one source file. But, it can also be co-locating logic spread across many source files in one folder (especially for non-trivial software logic). SRP ties perfectly into this by grouping (or co-locating) things that change for the same reason together and separating things that change for a different reason. When using SRP, people organise their codebase by. type instead of by feature - which is wrong. Organising your codebase by feature gives you higher LoB than than organising by type. So, you can only enjoy the true benefits of SRP by organising by feature (so you don't have to hop far around the codebase too much to understand the code). Making use of SRP in your codebase will always suck ass when you organise your codebase by type instead of by feature. Cheers ✌🏽
Nailed it. The more that the industry has become centered on social media celebrities and traveling conference stars, the harder it has been to keep new devs focusing on the tasks at hand. I’ve had more and more code reviews and one on one sessions with juniors recently where I have to emphasize that the customers don’t give a damn if the Gang of Four is happy with the code. They’re seriously encouraging people to overthink and turn 1 point Jira tickets into epics out of fear of violating some orthodoxy.
In my country, due to customer protection laws, invoices in B2C settings in some sense constitutes legally binding time-limited contracts, meaning it is important to save a copy of what was actually sent to the customer. Also, invoices in both B2C and B2B settings are used for tax calculations and by law needs to be saved in the format in which it was sent out or received up to 7 years after they were issued if the tax institute decides to make an audit.
It's funny, because the original meaning of "single responsibility" was that the code is responsible to/focused on the needs of one person/stakeholder/user, and has *nothing* to do with how the code is literally structured. From Wikipedia (but I heard this elsewhere, too): "The single-responsibility principle (SRP) is a computer programming principle that states that "A module should be responsible to one, and only one, actor." The term actor refers to a group (consisting of one or more stakeholders or users) that requires a change in the module."
When I went to make my CS degree we had a Software Architecture course and of course SOLID was teached. After that I tried to do it right, apply it wherever I can use clean architecture etc. I was so hard trying to do it right that I couldn't get done anything anymore. After a while I stopped caring and just tried to solve the problem with the simplest solution I could bring up. Productivity is way higher, the code is IMHO better too. At least if I work with other people they are able to make changes in my code that's a sign to me that the code is good enough.
Open-closed principle is described the wrong way in the article. It's about interface of class. Not about changing the code. It says on wiki: Keep existing interfaces. Add new if needed.
@@SimonBuchanNz The interface would be something like Open(string fileName). Should this interface ever have to be changed when the implementation is changed? If you need more ways to open a file than only providing a file name, you create additional functions with different parameters
@@gageduke7652 but adding interfaces is also changing code. Trivialy, but also compatible interfaces will be forwarding to a common implementation if you're being reasonable, so you're also changing the internals of the old interface. If you're also updating all callers to use the new interface, then you can remove the old interface, in a major version if it's public. In other words, if that's what it means, it's pretty obvious "I know how to program" stuff and doesn't need a confusing description like "open-closed" instead of just "don't break stuff".
Inheritance is very useful. Suppose you have a datastructure that combines multiple tables in a database because there is some validation across them. For instance a case-folder containing multiple documents, having a case-owner and some sort of access-validation scoped by user-session. Maybe you even have something stored in a completely different database, or off-site, like files for the documents in a one-drive or whatever. If you want to present this data in a web-application, you typically want to have API-endpoints returning DTOs of different degrees of implementation. I.e. one DTO that only exposes the most simple fast-accessed properties, and one or more inherited DTOs that expose further properties that the API might have to do a bit of work to retrieve the values for. That way, your front-end web-application can work with these different levels of inheritance and request the object that it minimally needs given the context, to display whatever it needs to display, and you don't have an API working overtime collecting data that is discarded.
Of course, you can always solve it differently. You could for instance separate out the properties that is load-heavy into their own structure, maybe it just takes in the case-folder id, and then you retrieve it completely seperately whenever you need it.
The biggest problem is that people simply don't understand SOLID. You write a class that has multiple responsibilities and someone says "that doesn't follow the single responsibility principle". Must it? "Um, yes because SOLID". If you can properly articulate SOLID, you end up striving towards SOLID whether you think about it or not. As Prime said, it's a North Star. I believe it's an ideal, one that you ignore when it's smart to ignore.
The problem with OOP is that when you write a family of classes, you've crystallised behaviour, it's great if you have minor addition/changes that you want for it entirely or upstream, but you haven't got wiggle room for anything in that crystal. The second part is that the crystal grows, over time you need something so you extend it and so fourth, so everything gradually crystallises (especially as the code becomes forgotten and people don't want to mess about with it, see adapters to pre-existing classes, it's like trying to untie a Georgian knot by hand to do otherwise). OOP works in this sense when you have something you know will absolutely not change or will only change everything (gamedev has a lot of this), but outside of that, it's useless and if anything, a blight as it becomes a monolith crystal. It even goes against agile-based workflows since you build a crystal structure that won't change, in an environment where the entire point is that you're constantly pivoting to your goal. I'll also add that FP has this same issue but the embedded nature of it is arguably worse.
You clearly didnt understand OOP at all. Not sure if this is because of seeing a bunch of cursed and plainly wrong usages of OOP or just bad teaching. But none of what you said about OOP is true if you do it right.
@@zeez7777That's always the thing, isn't it? To do it right. I assure you that you can do any paradigm "right", the focus should be how hard is to do it, the amount of friction and how long you'll need to go through trying to achieve that. Honestly, I prefer to follow all "best practices" somewhat loosely after trying them, thinking on how well they fit the context I'm in
@@gabrielfreitas4270 That's a kinda pointless argument. If you judge everything based on how easy it is to be misused, then you will eventually always end up only with the most fool-proof, but ineffective way of doing things. It's like stating that flying air planes is bad, because most people would instantly crash it.
@@Asto508 your analogy is such a strawmen. The whole point of what I said is to consider cost relative to what you'll get in return. It's not about how easy something is to being misused but how much would be the investment to be used correctly or on at an acceptable level. Nonetheless this for sure does not apply to everything, the context here is clear: software development. If you really want to go that route on generalizing into an analogy, a better one would be cargo shipping. For sure flights are great, but costly and may not fit well in many scenarios. As so, that's why we still have maritime and road transport.
Forget SOLID, go for this here new set of principles: Cohesion - Keep related functionality tightly bound within components or classes. Understanding - Code should be easily understandable by anyone on the team. Avoid convoluted logic, and write clean, well-commented code that others can follow. Necessity - Build only what is required for the problem at hand. Testability - Code should be designed with testing in mind, allowing for easy unit and integration tests. Scalability - Architecture and codebase can grow with the application's needs.
That article got me! Since it was giving specific examples from the same project, when it came to "L" I was on the edge of my seat. I know this project doesn't use squares. I'm going to see a new "L" example. But NOOO! I think of all the children, about to make their Square's inherit from Rectangle, being saved by the "L" in solid.
You are mostly working on code you wrote. At least in your streams. The vast majority of real world code I had to deal with is underabstracted, not overabstracted. Classes having 1000+ lines being a common thing, very low cohesion. You can't see "everything that happens within a single screen" like that.
I feel SOLID applies well at the overall class design level, but it starts to become a problem if you need to defer to these principals, line by line or property by property of a class. Take OCP. The first thing you need a clear understanding of is if, by design, you want the class to be extensible. If the answer is no, then relax the need for abstractions. SOLID needs to be applied in the context of an architecture, where you really want to focus on these principals where "parts" of your architecture interconnect, but within...you can afford to be a bit fast and furious with how you code things. And if you're wrong, then refactor.
Solid is a guideline, it's not something that you should adhere to to the letter as you're writing your code. It should be something that you work towards but not something that you start with.
You don't disagree with it. You just don't agree it needs to be done all the time. The take away is there's a right time to do SOLID. Let complexity emerge and then refactor as necessary unless you know exactly what and how complexity will emerge and SOLID principle just naturally shows as you code like you know exactly what you need to do. I've dealt with large projects and one way to tame complexities is to adhere to SOLID principles. In my 20 years of experience in coding, if the file is going beyond 400 lines, you should start rethinking about how you would want to break things apart. You will not wait for it to get to thousands of lines before doing that especially if your team has lot of juniors. Adhering to SOLID mitigates files from growing to thousands of lines.
I think that SOLID is flawed in a similar way to "Clean Code" or Database-Normlization. It is subjective and, opposite to its goal, can make things harder to read, understand, maintain and perform. Knowing about SOLID is important. Following it to an almost religious degree is not.
Knowing about SOLID is important simply to understand what it tries to solve (e.g. Prime still seems to have no idea about Liskov's), and understanding when to apply the principles.
Imagine a vector or matrix library with SOLID principles. They will literally want you to be making a whole file and class for each little thing you can do with a vector. This is my class that adds vectors, this is my class that computes the distance between vectors, etc etc. SOLID does one thing very well, spread your code so thin that it's actually not possible to understand what anything is actually doing.
SOLID says nothing about files. As far as your example, it really depends on whether those operations are behavior or not. For example, if you are making an application for vector math, where the user is in control of which operations to perform, then those operations would be behavior and might belong in separate classes, otherwise they are just operations on data. edit: another reason to use objects/classes, and which separates them from simple data, is invariants you need to maintain
It's not a violation of the Single Responsibility Principle to have all vector math functions in a single Vector class so as long as each of the functions can be used independently given that they are generic enough. Having Vector.Distance and Vector.Add in the same class and file for example is perfectly fine under SOLID.
Will watch later but here's my apriori take on SOLID: I think it has some interesting things to say, but it'd probably be easier to communicate / remind about the principles if someone were to translate them into English. EDIT: HAHA! First line out of Prime's mouth: "I forget what they are all the time" EDIT 2: Just noticed the article is different from the video title too 😄: ~"SOLID in plain English"
Thanks, fully agree. It's pretty wild to advocate for abstractions in a world where many coders can't even name a function properly, let alone prevent the name from being a straight lie after the third implementation change. Write a straight forward solution. Write it a few times. Get the functionality right. Get the performance right. Profile. Discover where abstractions can be of use and implement with care. Profile again. You know how to profile your project, right? Make conscious decisions based on the reality of your project about which actions and abstractions benefit your. specific. project. That's the only thing that matters. Also, a project driven by a single person will drastically differ from a codebase maintained by a thousand people. It most certainly did not enter this world looking like the latter, for very good and important reasons.
Locality of behaviour is just “I am too lazy to put functions into another file, just make code and don’t worry, because you are going to remake everything anyway”… except you won’t, and you will use this mess of giant files, with conflicts any time you touch the code, forever.
"Program until you NEED abstraction" is on point. I was having this debate at work yesterday, and I said something very similar. Unless you're a savant, who can architect a program entirely in your mind, the most logical thing to do is to write code until you realize that abstraction would be beneficial, implement it, and continue writing code. To do otherwise is just wasting time in my opinion.
Yes, but the important part here is to actually be aware of that and introduce the abstraction and refactor.
But you're right.
Big important problem with this statement: the savant also can't architect it in their mind. Nobody is that good. It's not a little bit out of normal reach to the point where you could get it if you had experience and thought reaaaaally hard about it. Requirements change.
spoiler: nobody is that savant
This is a good starting point, another is to abstract platform specifics. Abstraction can just be a C/C++ declaration too though.
@@zeez7777this is where it starts to crumble. It's tricky though. If you abstract everything beforehand, there is a big tendency to over engineering. If you don't do it there is suddenly no time and money for refactoring, and "it works" so why bother? But it works bad, slow, hard to read and maintain and it's crumbling.
I work in a game engine project which was initially designed with SOLID in mind. At the beginning a lot of those abstractions just seemed unnecessary. However, as the project grows and becomes really big it starts to pay off! It is just a pleasure to work with such a project. For example the basic windowing system was changed two times already from QT to SDL and than to GLFW. Nobody new this would be so at the start. However, as the windowing code is abstracted in a separate static library and communicates with core through small interfaces like IInputObserver which calls abstract functions like OnKeyDown/Up etc. changing the whole windowing system takes a few hours and produces no bugs AT ALL! This is just a simple example. The whole project is like that. A big project created with SOLID in mind is a pure gem!
I think this video is more about asking yourself a question if you need it in your project. Sometimes the answer is yes, sometimes it is no. Sometimes not all of those rules even apply, because OOP is not a magich approach to solve all problems.
yes, solid does work nicely in large and complex project, because with the liskov substitution you can go to a point where you can make most things modular
Indeed! Most people bitching about clean code and SOLID don't know how useful it is in big projects. ESPECIALLY in game dev, with a lot of passionate developers using 90% of his neurons to switch between shader colors instead of coding a good architecture.
Code quality in game dev is HORRIBLE. Once you start working with good standards, you'll never want to go back, because instead of feeling pain and agony developing your game, you feel joy. You feel pleasure. You find the bugs, you don't waste 5 hours trying to debug some shit code where you don't understand what is going on.
@@gustavosalmeron2013 You don't understand that codebase, if you take SOLID principals to their logical conclusions, assuming you didn't write everything in that project. SOLID doesn't result in quality codebases, I've been in too many over abstracted messes to count. There is no pleasure, it's pure misery.
@@cyberpunkspike Then you have the other way around. Over engineering is a waste of time just like under engineering makes you waste time. I have seem my fair share during my 5 years in the industry of shitty code. People should use some SOLID as a guideline, at LEAST the Single Responsibility principle. They could at least think before coding, that would be great.
I have also seem over engineered code, but at least they are approachable. It is much harder debugging spaghetti code.
This topic always reminds me of the famous Miles Davis quote: "Learn the score, then throw it away" is a quote often attributed to jazz musician Miles Davis. The quote emphasizes the importance of mastering the fundamentals and techniques of one's craft, but then being able to move beyond them and improvise freely.
😮 Great knowledge from my legend that I didn't even know. Thank you! Competent practitioners of most advanced skills tend to realize this one way or another.
True! But there are much more software developers out there who are not on this level.
My brother in Christ, SOLID is not fundamentals, it's literally the highest level possible trash knowledge that is as far from fundamentals as possible.
@@banatibor83 True. Nothing I've ever written is the 'Sketches of Spain' of software, so I keep learning, mastering and owning what I can.
It should be noted that there are also just flat out bad scores that no creative freedom can fix (without entirely replacing the original)
This reminds me of the book A Philosophy of Software Design. It states that you should only create an abstraction if it reduces the complexity of the system.
It calls abstractions that are a net drain on the simplicity of the system "shallow" and ones that are a net gain as "deep".
The book also acknowledges that this means that one piece of code may have to do multiple types of tasks.
Define "complexity".
@@Dogo.R The books definition of complexity is this. "Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system. Complexity can take many forms. For example, it might be hard to understand how a piece of code works; it might take a lot of effort to implement a small improvement, or it might not be clear which parts of the system must be modified to make the improvement; it might be difficult to fix one bug without introducing another. If a software system is hard to understand and modify, then it is complicated; if it is easy to understand and modify, then it is simple." -John Ousterhout
The book starts by introducing this definition and then goes on to describe how to recognize this complexity and how to mitigate it. I've been using its principles in my current project, and I've been really enjoying it. :) I feel like it helps make up for my lack of intuition as a relatively new programmer.
I shared the book here because I feel like a lot of what Prime says lines up with the book pretty well. If you like Primes software design philosophy maybe check it out for a more detailed and structured take on things.
This is a talk where he introduces his book ua-cam.com/video/bmSAYlu0NcY/v-deo.html
Essential complexity versus accidental complexity.
@@monad_tcp Yes, at the end of the day every system you create will have complexity. But you should aim to keep that complexity close to the minimum needed to get the job done.
When looking at Primes design principles through this lens you can interpret his idea that you should start with the "concrete" of a solution and discover the abstraction later as an attempt to limit complexity.
Similar ideas are explored in the book which spends a lot of time talking about how to spot bad abstractions and how to find good ones. However, there are some significant differences between the books philosophy and what I understand of Primes philosophy. For instance, the book is not as opposed to starting your design process by coming up with an abstraction.
@@rampantporcupineandfriends3793 You start your design process with something you know, which is something Prime seems to be struggling with in general, as seen by his arguments against TDD (assuming that you need to know pretty much the entire project before you can write meaningful tests, which is just an absurd understanding).
Sometimes you obviously know that certain abstractions are central to your project.
The whole OOP vs Functional thing just didn't make any sense to me when I first fell down the rabbit hole, but after hearing enough of it I've come to realize it's a false equivalency. Basically it's just click bait to get views on articles and videos.
I just write and organize my code for the best balance between readability and optimization, which turns out to just basically be Functional OOP.
Although, I do get people's beef with inheritance, and when I first learned OOP I way overused it. Since then, I only use inheritance when it's the best way to solve a problem, which is very rare, but very powerful in those rare situations. Yet it's another thing people want to fight about and claim a position of being anti or pro on... So it's no wonder all the software on my PC and phone are getting worse and worse with every new release.
Thank you! I had to maintain an overused inheritance project in Ruby once, it was horrible. Everything was hidden. I had to read code for hours and hours just to grok anything. Then I got reassigned to another issue that was higher priority in another project. RIP whatever issue that was, it's been a decade and I still remember the frustration.
@@samgraham182 Oh man, that's classic ruby. I don't miss working with it: bloated rails, annoying metaprogramming in the least expected places, "rockstar developer with god complex" coleagues, cultish "the rails way", hours long discussions about code style, etc.To hell with it
I dont really study programming too much (Im more of a make things work kinda guy, trying to change this). Im very much a vibes based programmer. The realizations you have come to are very close to what I have been leaning towards when building things. Locality of behavior is really important for me. The only reasons I break it is if the same logic is needed elsewhere and should be aligned. When I have to read 4 different files to figure out what one functions does I get very angry and want throw laptop.
when people do the "functional vs oop" thing, they are raising the problems with oop, then trying to come up with something to contrast it with. but structured programming wasn't en vogue anymore when these comparisons first cropped up, and functional programming seemed like the new hotness (despite being invented some time in the 50s through 70s). so fp got saddled with oop's baggage, being nominated as the next big island of thought to jump to.
turns out neither is a good dogma, and they're just different ways to add abstraction to a program. abstraction can be valuable, but it comes with a set of ongoing costs. it should be deployed in hesitation, when it produces significant value in return, when you have a provable, reasonably sizable, and current burden that it resolves.
I honestly don't believe that you only use inheritance very rarely. Every time you use an interface/abstract class, you are using inheritance. The Strategy pattern is one of the most useful applications of inheritance and it's rarely not useful at all.
It enables you to swap out implementations in parts of your code that don't ripple through other areas and that alone makes it such a wonderful tool for long term maintenance.
Liskov doesn‘t say that you want to have a lot of subclasses. It just says that if you write a subclass, you better don‘t do unexpected things there that breaks code which codes against the base class. If you never write any subclass, you trivially follow the principle.
The principle applies to interfaces just the same. In fact, it has more to do with function contracts which is what interfaces are all about
I think a big ding against SOLID is that theyre called "principles". Theres only one principle, and it is "manage complexity".
Maybe these should be called "perspectives" (the name Lens or Optic already got stolen by the FP folks)
And the perspectives are best understood by the outcome of violating them:
- SRP: no god classes
- O/C: avoid fragile base classes
- Liskov: dont have your subtype do something completely different
- ISP: no thousand method interfaces
- DI: dont couple to dependencies.
Ive seen time and again that avoiding this (decent, vague) advice, the worse and more convoluted the code gets.
That's very helpful, actually. The standard pithy description of these has been really hard to wrap my head around, especially Liskov. And at least for that one, I think I know why: that sounds like the most bone-headed thing ever and I'm pretty sure I've never tried doing anything like that.
It's a long-standing problem with OOP in general for me though-at least in the old days (90s/00s) when I was learning this stuff they _loved_ their jargon rather than speaking in plain english which made everything 10x harder than it needed to be.
Liskov is more like "Don't restrict your subtypes more than your base".
Said like that it doesn't seem so obvious, but it does avoid some issues.
that's a great way of looking at it
I find it so annoying to have the question "hm, database isn't saving correctly, let me see what happened" and then I need to open PersistenceManager, DatabasePersistence, InvoicePersistence, and Invoice before I can start debugging. Thanks SOLID, very understandable! Worst part is you'll go right to Invoice and wonder where the database saving is, and so you need to search for Invoice and then you find InvoicePersistence and then you realize that there's no logic, it's an interface. Now you need to search for InvoicePersistence to find where it's implemented, then you find DatabasePersistence. And then turns out the bug isn't there, it's actually in some random file that calls invoicePersistence.save() and it turns out someone passed a FilePersistence to the wrong method somewhere else and so DatabasePersistence.save() was never actually getting called in the first place.
Brilliant comment
Yeah but you need to first ask yourself why are you doing any of that. It isn't anything to do with SOLID. You have separated your code base into small easy to test units and then didn't test any of them. If you have to instantiate the entire system to figure out where a bug is occurring then something has gone seriously wrong earlier in development.
@@deloftie3619 You say that like `saveInvoice(invoice, database)` would be difficult to test.
OOP tends to be hostile to tests in my experience, because intractably bundling data and functions makes it very difficult to validate that the right side effects happened to the right objects at the right time. That's not me arguing for FP, but a strict separation of data and logic is literally always healthy.
I also, and I say this as a huge fan of testing, warn that your test suite will entrench any structural status quo that you test. This means that structural methodologies that cause large sprawling implementations (like OOP) will become calcified by your test suite. If you do try to refactor those abstractions later to be less cumbersome, you'll be glad the tests are keeping you safe, but they'll also double the effort of the project, and that's the trade-off.
@@deloftie3619 In my experience, the nasty bugs are between layers (of abstraction). Unit tests are fine, but automated integration tests that cover multiple layers (e.g. from UI to database and back) can be gold.
@@MaddieM4 "That's not me arguing for FP, but a strict separation of data and logic is literally always healthy. "
The problem with that is that in large systems the code becomes tightly coupled data pipelines that are very hard to change. Imagine it like stringing together a whole load of Linux commands with pipes and then deciding a few years into production that actually the data format between two of the pipes needs to change.
In OOP you are trying to, as much as possible, get rid of "data" as a separate concept to "behaviour". This obviously can be hard to achieve, particularly if engineers are used to thinking of systems as data pipelines with procedural functions manipulating a piece of data and then passing it on. But if you can crack it you find you can open up your system to be much easier to change and evolve. And all the SOLID principles are doing are showing how that can be achieved.
SOLID is an ideal. Experienced programmers who understand its concepts write SOLID code, but break the rules when they're not worth following. Teaching that SOLID is bad is just as harmful.
SOLID can be applied to every level of your program. You don't want to follow the rules 100% in every part of your code.
And what a lot of people don't understand, is that all the 5 principles are actually the same principle.
ONLY if you teach people that these ideals must not be followed unthinkingly, and provide hard examples of when the prudent action is to not follow them... then I'll get on board.
Model View Controller is a more useful design pattern.
@@hackmedia7755 Isn't the Model View Controller an example of some of the SOLID principles, specifically S and D?
@@gageduke7652 no, MVC is just one out of many design patterns that exist in Software Architecture. I think design patterns are more useful to think in.
liquid programming
Ahhh yes the Bruce Lee paradigm
Programming with inferior genes
naked programming
Solidus programming
venom programming
Prime is so correct at the end there. (Almost) Always write out concrete versions of your classes, then go back and abstract them. It took me 10 years to learn this (I am a slow learner). This way you know what parts are repeated a lot, you can then tease them out and abstract them as needed. Your overall structure will make more sense from the first abstraction attempt, instead of doing it as you go along, where you can't predict your ass from your elbow.
Could you please explaining this in more detail or refer an article that does?
@@sasieightynine I didn't read it anywhere unfortunately. It is just a pattern that I learned to recognize after making the same mistakes over many years.
I suppose an easy way of putting it is something like this:
Start by writing your program in the dumbest, most straight-forward way possible. Iterate upon this and try to tease out any repeated parts and abstract them, but only abstract them if you NEED to. Don't just abstract things for 'the future', it doesn't exist. You are in for a world of pain if you go down this path.
Instead, focus on the problem at hand, and possibly the most predictable outcomes if you have the resources to spare.
A lot of these seem to convey an emotion of "if you ever have to refactor this, then you made a mistake. You should predict and/or accommodate the future perfectly"
I'd much rather see principles around the idea that "you cannot avoid refactoring, the only question is how to make it as painless (or dare I say easy) as possible"
and a lot of that difference is diametrically opposed when it comes to the guidance on inheritance
All of the SOLID principles are to facilitate easy and painless refactoring. Most modern devs don't care that much about maintenance and refactoring though (they just made a pile of mud and then move project and/or job and start again) which is why you get videos like this that are saying don't bother with SOLID principles.
@@deloftie3619 In my experience solid principles make refactoring harder because more often than not you build the wrong abstraction, resulting in first having to undo the abstraction and then building the right one. In a perfect world where we can perfectly predict the future solid makes sense. Reality is very different though.
@@Chisegh I don't follow what you mean by "undo" the abstraction. If you follow SOLID principles you never need to change your existing code as your product changes or matures. You are only ever adding new code
It is the exact opposite of having to predict the future, which is what you have to do with procedural code because you have to either know that function X does everything it will ever do or you have to add new functionality to the existing function X as you develop.
This is because functions are tightly bound (it is difficult to say to your exist code "Stop using this function, use this one instead") so existing functions are constantly changing and growing, which massively increase the complexity of the functions and the risk that bugs will be introduced.
I've worked on so many software products where after a few years every function is a collection of if statements for all the new functionality that has been piled upon the function over the years
@@deloftie3619 to put it simply, I disagree.
"it is difficult to say to your code, stop using this function, use this one instead"
I only find that to be difficult when the functions entail large amounts of state, which is you guessed it, from OOP
The worst nightmare refactors I've ever seen have all involved unnecessary class hierarchies and the inability to fix problems without causing problems in new places, or ripping everything inside out to procedural again.
When procedural code just does what it says right in front of you, instead of reaching through so many abstractions, it is easy to refactor by comparison. Especially with a dash of functional programming to minimize use of state overall.
@@AndrewBrownK "I only find that to be difficult when the functions entail large amounts of state, which is you guessed it, from OOP"
Well its not state I'm worried about in this situation, its binding.
If you have a function that is called in a 100 places in your code and then you need to alter how some of those places behave you have a tough time about it. I've seen so many large functions with multiple paths that have been added over the years to account for new functionality that has had to be brute forced into the existing function.
And don't get me started about having to slowly increase the parameter size of a function as the function becomes responsible for more and more.
"involved unnecessary class hierarchies"
Well sure, the "I" in SOLID is essentially don't have class hierarchies that can break your code.
"When procedural code just does what it says right in front of you"
It does, and it is perfectly fine for small projects that won't change much. I've nothing against procedural code, I write C and procedural Python all the time.
But it is a nightmare to have to maintain procedural code in any large system (again binding) that grows and matures as the business requirements evolve. Which is exactly what OOP was invented for.
locality of behaviour is a thing - if you have to reach outside to a wider radius from where your working then your cognitive load shoots out the park - so to speak - which seems counter intuitive of the principle that we want our code to be easier to read and understand
This is the problem that we are stuck in this cycle of someone writes poor code and then someone else has to debug that code and find out why it isn't working. So everyone is constantly trying to understand what the hell the other person wrote. At this level having a procedural style "everything is happening in this one function" approach makes this easy.
The problem with that is that you end up with a God awful mess at any abstraction higher than the specific bug or issue the programmer is trying to fix. These apps become balls of mud, where fixing specific problems is trivial but any larger change becomes almost impossible.
So the cycle becomes an app that it is easy to fix what your buddy wrote a day ago but you have to throught he whole app out every few years and start again. Developers are so used to this cycle now that they see anything else as an impossible utopia
It's almost like people who created SOLID are in the business of consultancy aka never actually solving any problems because they need a next paycheck.
Imperative programming is very easy and straightforward, once you know your problem and your program, you map one on the another.
OOP is a cult where to solve a problem you need to travel to a shrine, pay a high priest a bribe, burn incence to the gods, hope for ten years and then interpret random events like proof it did actually work. Guess which one corporate parasites prefer.
Generally Liskov principle states "dont let the tail wag the dog", so hierarchy of classes should be in sane composition with each other. Rust traits are the good example of following the principle
I think `ThePrimeTime` should actually like the Barbara Liskov principle, as it WARNS YOU, that if you want to use objects and interfaces you need to obey the substitution rule.
With such a warning, it is clear that the bigger inheritance hierarchy, the more places that you need to obey the rule, the harder it is to write new code, modify existing.
My gut feeling is have a tendency to under abstract because abstracting from a concrete state is easier than to make concrete from an abstracted state. It almost always is a one way road.
In my experiences, good abstractions are easy to miss and non-obvious at first. Whenever you are adding or changing something in a class or function that requires another class or function, ask yourself whether you really need that other class or function or if you need *everything* in that other class or function. If you answer to the last part is "no", then there may be an abstraction waiting to be implemented.
Same would apply to unit tests. Try not to introduce other components that you do not intend to test in that instant. If you can't, then you need an abstraction.
It should also go without saying: Don't Repeat Yourself
There are basically three main (or meta) principles above all: KISS, DRY and No-Principle-is-Sacred.
I generally agree, but I have worked on codebases that were a nightmare due to taking DRY literally :)
DRY has its own problems and can lead to tight coupling so there's a balance to find there as well.
Fully agree to KISS. For me YAGNI is important too. But DRY is a difficult one. I generally won't repeat myself implementing the same logic in different places for the same types. But e.g. having the same string at different places doesn't bother me.
I'd say YAGNI is way more important than DRY
@@cod3r1337 for sure! I try to concentrate on KISS and YAGNI for my projects as well in my day job and private projects. And, what I in addition to that absolutely hate is when people are writing code in different languages in exactly the same way. E.g. writing a microservice in Go with hundrets of dependencies as they would in Node or Java. Go is all about KISS and that is absolutely ridiculous!
For me, SOLID is like a bag of tricks you can use to get yourself out of trouble. But if there's no trouble, you don't need the tricks.
You may be in trouble and not realize it soon enough before 100s of hours of refactoring are ahead of you.
Now THAT was SOLID video.
Thanks Primeagen, I've been coding for about 10 years now, have read about SOLID before, but I've never fully agreed to all principles, I like going with the flow, just like you, start with concrete and hands dirty right on, then I take a sense on what actually needs to be abstracted and move on, thing is I thought I was a poorer programmer cause I don't fully agree with SOLID, this video gave me a lot more confidence as I honestly agree with practically all the arguments you brought.
Being a good programmer is finding a balance in between strong principles and compromises, for the overall benefit around the application being built, our mental health, our future-self, project turn-over, quality, maintainability... a balance in speed and quality.
All this discusions is like discusion: Should you use tea spoon or not?
But this question don't talk about time and place of using it. Is it about eating steak? Is it about eating soup? Is it about drinking tea? The answer will be diffirent in all three situations.
You should not even try to use tea spoon to eat steak.
It may be possible to use tea spoon to eat soup if you don't have any better option.
You should use tea spoon for drinking tea (if you need to put something to tea). You should not use fork to drink tea.
You just need to find right time and place to use it. But depending at your situation you may not use it in your life time.
The comment about knowing when to abstract and when not to abstract really resonated with me.
My sense is that the SOLID principles become more valuable as your app gets larger and more complex. When you are first writing a small app, many of the principles cause more problems than they solve. When it is easy to execute on them, great, do it. When it is awkward or difficult to do it, wait until you actually have a need to actually do them.
Many times, the best choice is to just be mindful about how you can avoid closing doors on your design unintentionally.
I don't see anything wrong with SOLID principles. Single-responsibility: fine but quite vague. Open-closed: good. Liskov substitution: very important. Interface segregation: useful. Dependency inversion: very useful. I would definitely be a worse programmer if I didn't know about Liskov substitution principle or if I didn't know how to make use of dependency inversion.
"Duplication is often cheaper than the wrong abstraction"
Regarding pre-emptive abstraction:
You
Ain't
Gonna
Need
It
...until you do... But most people only get ~5% of such predictions right--which leaves ~95% useless, brittle, noisy wasted-effort if allowed into the codebase. As Prime said, by the time you have proof that the abstraction is needed, it's easy to extract out exactly what's needed from your initial concrete implementation.
I think those numbers are not really reflecting reality. Or maybe they do and I'm just biased. Depending on the domain and personal experience level, you can more or less safely predict future requirement changes and plan where abstractions might be needed and where not; and if you needed them in hindsight, you are glad that you prepared for it when there was plenty of time.
@@Asto508
The percentage definitely goes up with experience but it's also definitely low for juniors. One of the most significant ways seniors increase their odds is by simply making a lot fewer baseless assumptions/predictions.
Software engineering is fairly easy to change on short notice, especially if you're familiar with e.g., JetBrains' refactoring tools and also if the code sticks to what's actually needed / in-use. What's hard to change on short notice is architecture/infrastructure so it is good to get that as right as you can up front.
Still technology is complex enough that the first #83 features of a platform will align really closely with your requirements and then six weeks in you try feature #84 and find you got bit in the @$$ by it's marketing. Then you transition onto platform plan B because the large gap it has on feature #6 is actually easier to close even after re-implementing 🙄.
@@Asto508 it also heavily depends on whether the code is contained or spread through the code base like cancer, and on the flip side (somewhat) if there is some "god class" thing going on
@@defeqel6537 If the code has a high coupling, then that's just an example of doing it wrong and doesn't have much to do with pre-mature abstraction
@@Asto508 I agree, but your abstraction may be the interface of the concrete implementation too. As long as there is a single piece of code handling the functionality, it is simple enough to extract a polymorphic abstraction later on.
I think ThePrimagen should push his new paradigm: SOLIDR, single responsibility, etc, etc, etc and then R for "If all else fails just RAW DAWG IT IN THERE WHEREVER YOU WANT"
"single responsibility principle" doesn't mean the class should only do one thing (although uncle Bob argues for that too), it means it should only be beholden to a single stakeholder, i.e. it should only have a single reason to change. but you're right in that this isn't the law, if it makes a small app way more complicated for the sake of adherence to SOLID, then it becomes counterproductive.
Liskov is for Barbara Liskov :)
Finally someone who gets it. The biggest problem with SOLID is that it is being misunderstood at the most fundamental level.
Gather together the things that change for the same reasons. Separate those things that change for different reasons.
Exactly. The architecture of your software should mirror the business units of your enterprise depending on it, or the diversity of your stakeholders in general.
"beholden to a single stakeholder". Have you thought about what that means critically?
@@chickenmonkey88 your mom
This is essentially the programming version of the Platonic Idealism vs. Aristotilian Realism debate
Jesus these bots be saying anything, gosh
Googles priorities are in the wrong area 😔 they can shadow ban us for saying cursed words but can’t do that with bots 😂
Thumbnails ok tho 🤣😎
i am a BSD enthusiast
Jesus these bots be saying anything, gosh
Jesus these bots be saying anything, gosh
Hi Prime, generally I like your take on programming, but on this one, I kind of disagree with some of your points.
The Single Responsibility Principle might seem like overkill for a small project and I agree with your stance, but as the project grows and its components become critical and sensitive, separating concerns becomes more valuable. This helps to prevent one part from crashing the entire system. For example, if the rendering layer crashes because of issues with the cache, it doesn't make any sense.
The Liskov Substitution Principle isn't about advocating for inheritance per se, but rather highlighting some of its challenges. Classes should be inherited based on their behavior rather than just their properties. If class A inherits from class B, we should be able to use B wherever A is used. In this regard, using composition instead of inheritance makes sense. Using an interface or trait enables us to define a contract for class behaviors, so if A and B used interface/trait, they can be exchange
As for the Open-Closed Principle, I don’t think there can be a valid argument against it. As you are saying everyone uses the strategy pattern, everyone uses the Open-Closed Principle. "Closed" refers to consuming an API, while "open" pertains to building this API. When we say "closed," we imply that the main logic of our application must be “stable” and our main free from considering implementation details. For example, when I use a library, an API or even a (high-level) language, I focus on the algorithm; I write it, my logic is correct, so my code is completed. If there's a bug or a need to optimize the tool, change the database, or tweak something under the hood, it's not my concern-it's implementation details. And if there’s a new feature landing on the API, it’s good. But the main logic of my application shouldn't change because of such changes.
My general stance in programming is just don't over-abstract, don't jump the shark. There're good patterns and good practice but not always necessary.
Very well articulated and I agree on your critique.
Inheritance is just one way to implement polymorphism. If you need polymorphism, then the complexity will accrue somewhere, in the class hierarchy, a switch statement, or elsewhere, you can't really avoid it.
Clean Code actually has a decent section on this: "Data/Object Anti-symmetry"
Polymorphism is so niche technique, you may not have to use it even once in a lifetime.
And this applies to all SOLID principles, but people can't just sit straight and do nothing 5 minutes...
@@Georggggnot for your app (unless you support modding/plugins), but everyday for libraries
In my experience there are VERY few hard rules or principles one should follow, but a lot more guidelines.
When I first started programing my OOP inheritance tree would make the Habsburgs blush. And then during the functional renaissance I tried to use it everywhere and it didn't work out. because a functional approach didn't fit the problems.
Remember paradigms serve us to solve problems we don't serve paradigms, use the right tool for right problem and don't be dogmatic.
Some problems are easily solved by inheritance, or composition/interfaces, or a functional approach.
And experience and your personal discretion will help you make that right choice.
Because all of them are solutions but also constraints on otherwise possibleto calculate code. Same as languages, ideal one would allow me to do anything, best up to declaring new keywords andsemantics if I need to while preventing me from usinng that on accident.
The problem with SOLID is that one needs a lot of experience to understand and interpet it in a way that's actually useful, but if you have this experience you don't really need SOLID.
This is not a problem. This is true everywhere in software. Experience is key to everything. Principles written in books are worthless without it. That's why in his Book "Clean code". Robert C. Martin goes into extreme lengths to explain that we need to think hard about and experiment long with the SOLID principles he is proposing. Nowadays, they are just catchphrases used in interviews or in Pull Requests to make an argument...
I kind of agree to a certain degree. Single responsibility and dependency inversion are pretty easy to understand and don't require much experience to benefit from.
@@Dom-zy1qy of single responsibility is so simple why do so many people, including the article in this video get it wrong?
@@damdoumibou3ajaja338 Robert C. Martin recommends principles that can not be implemented well regardless how much experience you have. 12 lines per function is just the wrong foundation no matter how you apply it.
I think of it as "SOLID adds no marginal value over what you already should learn". For example, that part where the article says "logic and a database in the same class violates SRP". Well, basic OOP already says to de-couple by slapping an abstract interface over the DB (if the project is large, mostly in the final state, and expects to swap out different databases). Thinking of that as fixing an SRP violation adds nothing useful. Likewise, learning about public-interface/private-implementation is worse if it starts with "here's SRP, now we're going a learn a technique to avoid it".
30:40 this!! it’s the spidey sense that you might want to extend later, but enough experience to hold off. you don’t make it impossible to extend, but you also don’t try to make it extendable in every possible dimension
the problem is not solid, the problem are programmers who don't know how to produce code, that is in anyway extensible, modular or (i hate to say it) maintainable.
that's why SOLID is needed to teach those idiots what not to do, if they get it, they are allowed to break SOLID principles, and make their own decisions, until then I'll keep treating them as little children.
I've seen so much BS out there in the wild that I have PTSD.
- handwritten, xml parsers,
- functions with 3k+ lines to generate templates for templating engines,
- 300 lines of imports in java class files,
- functions with 17 boolean parameters,
- testcode in production,
- orm.execute("select * from xxxxx").isEmpty(),
- 10 levels of class hierarchy
- formatting/display logic in sql statements
- true means true, false means true, null means false
as long as simple concepts like inversion of control, dependency injection, function composition, separation of concerns are not known, we as an industry have to teach SOLID.
your voice is smoother since quitting Netflix, faang bad for health confirmed
oh i didn't know he quit
Incroyable.
He quit?
@@TheArrowedKnee yup
I feel that the real problem is the appropriation of existing connotation-laden english words like clean, or agile, or solid. If he called it Robertist Code instead of Clean Code, or Martinist Manifesto instead of Agile Manifesto, or UncleBobist Principle for solid principle, I would have nothing against it. Just imagine how creepy it would be if religions were named using existing everyday words - say if we had to use the word "genuine" for "Christian" or "honest" for Hindu and so on.
People criticize OOP because it hides details so you need to switch to a different file to find those. Yes, and that is a good thing! When you read a program you should be able to understand it’s domain logic - the purpose of your program without knowing which JSON format files are written och read from or if the server runs HTTP/2 or HTTP/3.
That being said I don’t like inheritance , classes should only implement interfaces, not extend abstract baseclasses.
Functional programming can help you avoid a lot of extra classes, so it is good to mix it in.
If you ever worked with the Linux kernel that is purely written in C and scattered around thousands of files, you would instantly realize why it makes zero sense to equate OOP with having code in multiple files.
@@Asto508Sorry, I think I was misunderstood. I did not equate OOP with using many files, you can put everything in one file if you want, but it turns into a mess.
Second, the Linux kernel IS written in an object oriented style, emulating classes with macros, structs and function pointers.
My first rule of programming is write reusable code. Reusable code makes it easy to not repeat yourself, it is easy to "reuse" in tests to verify it, it is easy to reuse existing code when refactoring into new abstractions. After finishing a function or class if you have a hard time imagining that thing being used outside of the specific use case it was written for then you should rethink the design.
3 seconds in, true.. my only problem with the SOLID principles is that I forgot what they stands for few hours after reading about it
true
Totally agree to all the statements in this video! Before you start programming you have to think about what you want to deliver. Do you want to build code that is very flexible and general because it will be part of a project that changes the next 10 years or do you want to deliver specific code for a specific problem that will not change. I almost always choose the last solution which means less code, less (abstract) thinking and less effort regarding time spent which means being very efficient and very cost effective. And this year my new friend ChatGPT solved some annoying or complex coding problems for me (but you have to know how to use this tool) so this makes it even more efficient.
You can do SOLID in functional programming too. Interface = higher order function.
the last year I've been dealing with a project of like 16 microservices in typescript, all using really dense solid patterns... every opportunity to use an interface or a factory was taken, even if there's just a couple entities involved. Want to add a new database table? get ready to write 7 code files to follow the pattern. want to find a bug? get ready to drill down through layer after layer of abstraction. I mean, the code can be scaled out into infinity and beyond, with multiple teams working on it happily... but its a 300 user internal app with a pretty limited scope, and like one or two developers. I've really come to absolutely worship simplicity.
In my career, I've far more often encountered code that was under-designed than over-designed. While what you describe sounds crazy, try to be somewhat accepting of it. At least the developers tried to segment and structure their programs. We all get things wrong, but appreciate the attempt. I've worked in so many code bases where the developers didn't care at all about design and just wanted to count story points as done.
Your example sounds like the opposite of SOLID. If you have to change 7 code files or mess with multiple layers of abstractions at once just to implement or fix something, then you failed in creating good abstractions while violating the Single Responsibility Principle.
I love Prime, I used to call it: this is over engineering.
Or why are you giving me a spoon larger than my soup bowl?!
Abstractions like the ones SOLID calls for make you *feel* like you’re making things easier and more maintainable.
Really, you’re just hiding the actual function of your code underneath a coat of pretty paint.
hmm, that depends actually
I feel like this is very true for single responsibility. One thing that is rarely noted is that separation of concerns is mutually exclusive with locality of behavior. So you look at a small class that does only one thing and think "oh, this is simple to understand." But then you start actually working with the codebase and realize that a single process will span several files and contain several layers of indirection that make it very hard to track the actual control flow (made even worse if you add inheritance).
Conversely, procedural code with high locality of behavior often looks ugly and disorganized, but when working with it you realize its very easy to read and understand all the code paths.
There are tradeoffs with both, but I personally consider locality of behavior to be the most important, and only move away from that of testability becomes an issue.
That's only true if you're doing it wrong. There is overengineering and pointless abstraction, but there is also the opposite that is equally bad. If an abstraction is not serving a real (foreseeable) purpose, then it's just a useless layer and simply a wrong application of what abstraction is supposed to solve.
The hammer isn't bad because you try to drive screws with it tbh.
You robbed my face. Please give it back. :(
@@LusidDreaming complex control flow often indicates poor design; even with multiple modules participating in the control flow, the flow should be (mostly) one way, and this is makes things relatively simple regardless of how many modules there. That said, there is generally no reason to split things into multiple files until those things are actually needed from multiple places, perhaps not even then.
Ive always felt bad that i typically ‘script’ much in the way that i see you’ve done here taking the tradeoff for ease of writing vs strictly adhering to these principles, glad to see I’m not the only one! #gatekeepingprogramming
So essentially you use all these princuples but you don't apply to cases where it doesn't make sense, like everybody else does. OMG, what a discovery! Everybody knows you shouldn't apply a tool where it doesn't make sense and that's why it's called "principle" not "rule you apply to 100% of cases". Sensationalised title just for views and clicks that's supporting SOLID with the title "Solid Programming - No Thanks". These kind of posts hurt the industry and creates polarisation in regards to engineering principles. You're turning this into politics for personal reasons, what a shame.
Womp womp, tard
Amen, "you abstract when you see the abstraction" 🙏
Solid is great, but not when you are dogmatic about it. Always think about YAGNI and KISS whenever you want to abstract. Good devs know solid rules, the better devs also know when to break them.
I teach it like this. Writing software is a lot like music, we learn foundations and guiding principles, structure and theory. Once you developed mastery in those areas, go ahead and break the rules. That's where the magic happens. But I'll be easier to ease back into the foundations if things go sideways than it will be to establish foundations when you started out without the rules in mind. Much like art, negative space is also important. It's ok to leave things out. You don't have to be comprehensive if code is going to be unused.
Worse yet though is someone who refuses to know the rules. They can be dangerous contributors and toxic collaborators
ive seen people take Single responsibility too far in our codebases. for example. there was this part of the project that would transform a grid of points. ie rotation and translate.
these are all simple concepts that can be done in a few lines. whoever originally wrote it did every math operation in its own function. every function was a single line.
the problem with the idea of single responsibility is the line is ambiguous. because, technically every function had a single responsibility of a single math operation, and doing this made the code much less readable, who could have guessed. end of the day the programmer still has to understand where to draw that line, where abstraction is a benefit and where it is not.
in some ways these principles we have do end up feeling like they say a lot of nothing as there is no one size fits all and you end up losing the nuance to the when and why
Thats not the SRP concept, youre wrong, it's not about to split every task in functions, it's about every function only would have a reason to change, no many reasons.
Seems totally fine for your grid math lib to be split in multiple functions though?
Unless you meant in the sense that you had a specific set of operations to perform in for the final output nad they split that pipeline up for no reason? IF that's the case then yeah that's wack as hell, that's why I tend to describe my functions in terms of "data op" "pipeline" and "transform" the math operations (rotate, translate, etc) would be a transform and seperate but the pipeline for the final output would be in its own thing (func calc(in){in.rotate(1, 2).translate(3, 4).rotate(5,6)}) and the data op functions would befor getting the in object from a data source and another for sending that off somewhere
SRP is not separation of concerns. They are different things but are confused all the time. Everyone thinks they know what SRP is but no one really does. It's an organizational principle, not directly a code related principle.
The way I understand it the best is that you don't want multiple stakeholders requesting things in a single software entity (i.e. but not necessarily a class), as they are likely going to request things that conflict with each other.
@@TurtleKwitty yeah, it was basically taking one 2d coordinate space and mapping it to another. just a linear transform and a rotate
i was tasked to convert it from only being used to converted between two hardcoded coordinate spaces to be user controlled at runtime.
it did the latter, there were like 16 functions responsible to convert between the two. so it was like func convertToLocationX(x){return x + LOCATION_X_ORIGIN} and below it func convertToLocationY(y){return y + LOCATION_Y_ORIGIN} rinse and repeat
Makes sense if each of those function are generic and reusable. However, If those functions were built only to serve another function or to be used to execute a single task once... yeah that's weird.
Appreciate the solid reflections on this topic. Hard to get inputs from practitioners sharing examples of how to effectively apply these principles in the real world
2:06 This is the main point. Using SOLID principles is great and all but if you’re implementing abstraction after abstraction where the readability, testability, and maintainability suffers, it means you need to scale back and refactor. But more often then not SOLID principles can help you achieve those goals but like many other aspects, developers can take things too far. It’s the old adage “if all you have is a hammer, everything looks like a nail” (and that’s how I feel about functional programming also)
17:17 “you abstract when you realize the abstraction”
Love this principle. I’m building some Rust code to translate data between two programs, including a common format I made to make processing easier. During that process and discovery of how the two applications have their data formatted, I’ve discovered ways I can abstract and make common functionality between the two. I didn’t start with that, but discovered it as I realized two things worked similarly.
Also the abstractions under Single Responsibility are totally not needed and the exact reason why I hate Java. I shouldn’t need 5 classes to read a text file
If you do it wrong, do you wind up with SOLID waste?
Nowadays a lot of people brag about SOLID principles, not noticing that they already integrated it into their unconscious skillset
My preferred programming parading is FUNtional programming. Not to be mistaken with functional.
Here's my design principles: Make it as simple as possible without compromising function. All other design principles are sub to this, and are used when they support simplicity in a given context.
If you have 2 pieces of related code with very similar functionality, which is the simplest: to use shared data+code for the similarities, or to implement them separately?
Liskov principle just means that if you have, for example, an abstract base class (MeleeWeapon) that implements an interface(IMeeleeWeapon), you can have several concrete classes (LongSword, Axe, MorningStar) and, since you've coded to interface, any one can be dynamically selected at runtime and your code will handle that particular instance object since it WILL BE a Weapon/IWeapon. Beats the hell out of 800 lines of nested if/else statements.
Liskov's is a bit more involved than that (and applies to all polymorphism): e.g. if the client code expects the server code to always create a specific file on the filesystem, then that must happen in all sub classes
Completely get rid of the abstract base class: what matters is that the interface the concrete classes implement comes with a set of constraints that the implementations of that interface must adhere to. Implementations that stick to those constraints are well behaved and allow code that uses that code to make certain reasonable assumptions. If your implementations break that contract, then they are not substitutable with implementations that adhere to it. That's LSP in a nutshell.
About SOLID programming being good but preferring to be loose and not a requirement. I think this also applies to programming paradigms in general.
Yea, you can program every single thing strictly following OOP.
Or you can realize, this little thing is only in used by this 1 class and can be a single function.
This little task doesn't even need its own function.
Paradigms and these approaches to organizing code or guidelines, they're all tools. A good programming knows which tool to use for a particular task.
As a general rule of thumb, yea single responsibility is a good idea.
On the job, you're balancing readability, time to write & design, effectiveness, maintainability, performance and whatever other things I missed.
OOP is pretty bad when you build something under the philosophy of "I want this behavior to be standard everywhere" but then it turns out that behavior varies slightly and need independent implementations that you're trying to wildly tie together in dumb, complex ways. Procedural is bad when you say "I'm just going to build what needs to be done" and then you end up with a system where changing one flag means you need to track down 30 different places in code to add the same if statement in every independent implementation and everything burns down if you miss one. As prime says, it's all intuition, you're going to need to be able to make reasonable assumptions about how a particular problem is likely to evolve as it grows, and use the correct approach accordingly.
Your first sentence is literally what OOP is not about.
One thing that OOP tries to solve in an elegant way is the need for polymorphism, so exactly that you need partial different behavior within an otherwise same-behaving object. It's exactly one of the strengths of inheritance to allow specializations that stray away from the generic inherited parts. It's the whole point to allow easy modification for "non-standard behavior" instead of creating a whole replica that only differs on a miniscule level to its parent.
Being too stubborn with the "this should be standard everywhere" will absolutely be a terrible idea. You need to be flexible.
Sounds like your understanding of OOP is missing an understanding of the Single Responsibility Principle and Open/Close Principle
"track down 30 different places in code" -- lol, and you wish it's just in code, and not in DB schemas, logs, maintenance scripts running on some forgotten raspberry shoved in between drywalls, frontend (JS *and* CSS).
say, adding a message severity level seems trivial but it can become a *real big adventure*[tm] real fast.
or worse -- slow, with bonus side quests such as angry customers or dozens of people standing around awkwardly in warehouses being paid for doing nothing because what? you did *not* know that `danger` is a valid severity? you were naiive to think that it looks like enum, quacks like enum, but no oh boy this string is free like the bird and yes the whole frontend went down because, and only because you did not fall back on unknown string when f**ing switching style=`color: ...` in Vue?
(i know, weirdly specific -- it did not happen in production to me but it drives me up the wall how easy it is to make exactly that mistake, when working on "hey do not over-engineer me" kind of codebase)
@@Asto508 aside, but I recommend Christopher Okhravi's last video on this topic, "Only Use Inheritance If You Want Both of These".
i love his channel, especially the last "wave" -- he came back after a long hiatus and now he's just killing it with much smaller and focused videos
Totally agree with you, I like my program with very minimal classes, otherwise, it gets confusing really fast.
I can have classes (as I make the embedded programs most of the time)
LCD_Display class, Communication, GPIO, sensors, that is it, I would not add extra things to make it more confusing.
I think the sad thing is a lot of the time that "amateur" 300 line function we wrote when we started is where we come back to when we realise having 1000 lego blocks all over the floor is not nicer than having one lego millenium falcon. Your lego blocks are the primatives of the programming language itself, the for loops and the structs and whatever. You don't need to make your own weird duplo blocks.
Nah, definitely should have a separate InvoicePrinter class to handle printing 🙂
In my real world enterprise, SOLID applies very much, especially open/close. Mainly because we are at a point where we can build solutions that leverage utility computing services, so decoupling code for quick and easy extension of a persistence manager is very useful, for example, we might use an AWS implementation of a service today, but next month we want to switch to the GCP implementation, in large enterprise where we're continually looking for cost efficiency, we would do this kind of thing quite often.
When you're a long term employee of an organisation and you know how the organisation works, proactive abstraction can be easier and saves time.
I failed at so many tech interviews because these stupid questions about OOP, Clean Code, SOLID, or Scrum keep coming up. And as soon as I explained to them categorically why these things suck ass (with concrete examples) they immediately become hostile.
Alright lets hear it, in what way is dependency inversion a bad thing?
@@zeez7777 Alright, when I dependency inversed your mom.
THEMS FIGHTIN WORDS! HOW DARE YOU EXPOSE THE HYPOCRISY! THEY WENT TO CONFERENCES AND READ ALL THE BOOKS! THEY KNOW THEY ARE RIGHT!
I can understand Scrum but why the others? Actually, I am curious because whenever I am like, we should do SOLID, there is push back, "we need our code done yesterday." Which it could be but any modification would take forever. SOLID has consistently made me the least sad when I need to update code, if I need to update code at all. Best case, I am simply changing out the implementation and moving on.
It is funny how people use and love frameworks but decry using the same principles and practices that make the framework so great.
Golang will have you doing SOLID without even trying. That is what is beautiful about the language. I learned more about how to do OOP right from Golang than even OOP languages like Java or PHP. If you want a bad time, you will NOT be following SOLID principles in Go. Provided Go is best when you are doing a more Functional style but hey, you already have objects just out there why not add some function receivers and add some functionality to those objects? Or don't. Possibly Swift is on the same level of extensibility.
Your enthusiasm is infectious. Love the content, man
This is the classic YouArentGoingToNeedIt argument of Extreme Programing. I used to spout the "gospel" of XP early on until I understood it's major flaw - the refactoring nightmare. A collection of DoTheSimplestThing turns into an unreadable and unmanageable mess so slowly, you often don't see it until the fixes are enormous. The hidden part of "DTST" is that you have to refactor as you go, and knowing when to do this isn't trivial. This is the hard part.
SOLID it kind of the opposite. The SOLID approach requires a fantastic base to even work. There are few people that can design a non-trivial API/Library/OO base that's flexible, easy to extend, and doesn't need to be changed. This is the major flaw of the paradigm; bad choices early on tend to become permanent.
I don't think the point of SOLID is to make sure things don't change, but rather, to make future changes easier.
Perhaps the most SOLID principle is knowing when to set the rulebook aside and just get the job done.
Half through the video. My observations are: in case you are a single person who works on the project, then yes, no need to care so much. But if you are a team (which is preconditions for article) you should definitely stick to S principle. Otherwise it will bite your ace. It will lead to bloating code or huge duplication. Ofc, covering S module with tests is highly needed or you won't be able to understand how your single unit, which is used across multiple places will change the behaviour.
Once again, I create tests when I could not make it in a first try is kinda strong when you are a single person working on the project, but a bit silly when there are several people included. What if THEY would not be able to make some function in a first try?
I have exactly the opposite experience. Overabstracting doesn't make code more readable and thus team work not going smoother.
It is really cool how you understand the ideas behind each language. As a go developer i feel well represented by you =)
Having been forced to follow solid I can say the "single principle" just leads to a lot of middle man classes that's sole purpose is just to be the bridge to how to classes interact. Sometimes you can get away with just using a event/messaging system but when the coupling is tight then the easiest and simplest solution is just passing in a pointer. At least in C++. But when you are forced to follow solid that is frowned upon and you have to write classes with long ass weird names that only confuses everyone after a month of not touching it.
Then the people who forced solid on you didn’t understand the S.
@@avwie132 Neither did Bob Martin
the only time i ever use inheritance is encapsulating logging into errors and creating a typed instance of a generic typed interface, like something becomes somethingInt : something
I want all the SOLID advocates to make a game. Even something "basic" like Tetris or Arkanoid.
And then still advocate for their one size fits all solution.
For every rule in programming there are exceptions. I prefer to look at writing code as just a selection of rules of thumb, where there are preferred ways to do it and non-preferred ways that are ranked in order of most to least appropriate. But sometimes you have to select the thing at the bottom of the list because that's the only way you can reasonably implement it. Sometimes a function HAS to do more than 1 thing for efficiency reasons because otherwise you'd be looping through the same data multiple times. Sometimes a function HAS to return more than 1 value for that same reason. As long as you understand the code this is perfectly valid.
The art of programming comes in when you get into code organization.
Art? Maybe. Engineering? Definitely not.
im a noob, explain it pls.
@@kaanozk the only things that truly matter are correctness and performance. Source code file size, line length, formatting style, naming conventions, build system, number unit tests, all don’t matter.
@youtubeenjoyer1743 hmm ok, I personally would say "what ever gets the job done" and optimize from this point onward. Doesn't seem to be far off from your idea.
@@youtubeenjoyer1743 No. That would indicate your art skill level for programming is "crayons" and you're not a professional.
Here's my 2 cents on the "S" in "SOLID":
Single Responsibility Principle (SRP). I believe (based on my experience) that SRP does a very wonderful and perfect handshake with High Locality of Behavior (LoB). Now, most people like to define LoB as co-locating all the logic in one source file. But, it can also be co-locating logic spread across many source files in one folder (especially for non-trivial software logic). SRP ties perfectly into this by grouping (or co-locating) things that change for the same reason together and separating things that change for a different reason.
When using SRP, people organise their codebase by. type instead of by feature - which is wrong. Organising your codebase by feature gives you higher LoB than than organising by type. So, you can only enjoy the true benefits of SRP by organising by feature (so you don't have to hop far around the codebase too much to understand the code). Making use of SRP in your codebase will always suck ass when you organise your codebase by type instead of by feature.
Cheers ✌🏽
SOLID is the best example of "Conference & Book Driven Development"... ask Bob how and why he came up with the L in SOLID?
Bob didn't come up with any of the principles, especially not Liskov's, which is attributed to, you know, Liskov
@@defeqel6537 the idea to include it in SOLID
Nailed it. The more that the industry has become centered on social media celebrities and traveling conference stars, the harder it has been to keep new devs focusing on the tasks at hand.
I’ve had more and more code reviews and one on one sessions with juniors recently where I have to emphasize that the customers don’t give a damn if the Gang of Four is happy with the code. They’re seriously encouraging people to overthink and turn 1 point Jira tickets into epics out of fear of violating some orthodoxy.
In my country, due to customer protection laws, invoices in B2C settings in some sense constitutes legally binding time-limited contracts, meaning it is important to save a copy of what was actually sent to the customer. Also, invoices in both B2C and B2B settings are used for tax calculations and by law needs to be saved in the format in which it was sent out or received up to 7 years after they were issued if the tax institute decides to make an audit.
It's funny, because the original meaning of "single responsibility" was that the code is responsible to/focused on the needs of one person/stakeholder/user, and has *nothing* to do with how the code is literally structured. From Wikipedia (but I heard this elsewhere, too):
"The single-responsibility principle (SRP) is a computer programming principle that states that "A module should be responsible to one, and only one, actor." The term actor refers to a group (consisting of one or more stakeholders or users) that requires a change in the module."
That's a remarkably useless principle.
When I went to make my CS degree we had a Software Architecture course and of course SOLID was teached. After that I tried to do it right, apply it wherever I can use clean architecture etc. I was so hard trying to do it right that I couldn't get done anything anymore. After a while I stopped caring and just tried to solve the problem with the simplest solution I could bring up. Productivity is way higher, the code is IMHO better too. At least if I work with other people they are able to make changes in my code that's a sign to me that the code is good enough.
Open-closed principle is described the wrong way in the article. It's about interface of class. Not about changing the code. It says on wiki: Keep existing interfaces. Add new if needed.
This makes no sense. Do you mean just keep compatibility to reduce churn?
Wiki might be wrong, OCP is indeed about not touching "working" code when adding new functionality. This, of course, is not always possible.
@@SimonBuchanNz The interface would be something like Open(string fileName). Should this interface ever have to be changed when the implementation is changed? If you need more ways to open a file than only providing a file name, you create additional functions with different parameters
@@gageduke7652 but adding interfaces is also changing code. Trivialy, but also compatible interfaces will be forwarding to a common implementation if you're being reasonable, so you're also changing the internals of the old interface. If you're also updating all callers to use the new interface, then you can remove the old interface, in a major version if it's public.
In other words, if that's what it means, it's pretty obvious "I know how to program" stuff and doesn't need a confusing description like "open-closed" instead of just "don't break stuff".
Inheritance is very useful. Suppose you have a datastructure that combines multiple tables in a database because there is some validation across them. For instance a case-folder containing multiple documents, having a case-owner and some sort of access-validation scoped by user-session. Maybe you even have something stored in a completely different database, or off-site, like files for the documents in a one-drive or whatever. If you want to present this data in a web-application, you typically want to have API-endpoints returning DTOs of different degrees of implementation. I.e. one DTO that only exposes the most simple fast-accessed properties, and one or more inherited DTOs that expose further properties that the API might have to do a bit of work to retrieve the values for. That way, your front-end web-application can work with these different levels of inheritance and request the object that it minimally needs given the context, to display whatever it needs to display, and you don't have an API working overtime collecting data that is discarded.
Of course, you can always solve it differently. You could for instance separate out the properties that is load-heavy into their own structure, maybe it just takes in the case-folder id, and then you retrieve it completely seperately whenever you need it.
This guy should ditch programming and go into stand up comedy.
... Might be too niche an audience to support a family with
Go mentioned?
The biggest problem is that people simply don't understand SOLID. You write a class that has multiple responsibilities and someone says "that doesn't follow the single responsibility principle". Must it? "Um, yes because SOLID".
If you can properly articulate SOLID, you end up striving towards SOLID whether you think about it or not. As Prime said, it's a North Star. I believe it's an ideal, one that you ignore when it's smart to ignore.
The problem with OOP is that when you write a family of classes, you've crystallised behaviour, it's great if you have minor addition/changes that you want for it entirely or upstream, but you haven't got wiggle room for anything in that crystal. The second part is that the crystal grows, over time you need something so you extend it and so fourth, so everything gradually crystallises (especially as the code becomes forgotten and people don't want to mess about with it, see adapters to pre-existing classes, it's like trying to untie a Georgian knot by hand to do otherwise).
OOP works in this sense when you have something you know will absolutely not change or will only change everything (gamedev has a lot of this), but outside of that, it's useless and if anything, a blight as it becomes a monolith crystal. It even goes against agile-based workflows since you build a crystal structure that won't change, in an environment where the entire point is that you're constantly pivoting to your goal.
I'll also add that FP has this same issue but the embedded nature of it is arguably worse.
You clearly didnt understand OOP at all. Not sure if this is because of seeing a bunch of cursed and plainly wrong usages of OOP or just bad teaching.
But none of what you said about OOP is true if you do it right.
@@zeez7777That's always the thing, isn't it? To do it right. I assure you that you can do any paradigm "right", the focus should be how hard is to do it, the amount of friction and how long you'll need to go through trying to achieve that.
Honestly, I prefer to follow all "best practices" somewhat loosely after trying them, thinking on how well they fit the context I'm in
@@zeez7777 real oop has never been tried
@@gabrielfreitas4270 That's a kinda pointless argument. If you judge everything based on how easy it is to be misused, then you will eventually always end up only with the most fool-proof, but ineffective way of doing things. It's like stating that flying air planes is bad, because most people would instantly crash it.
@@Asto508 your analogy is such a strawmen. The whole point of what I said is to consider cost relative to what you'll get in return.
It's not about how easy something is to being misused but how much would be the investment to be used correctly or on at an acceptable level. Nonetheless this for sure does not apply to everything, the context here is clear: software development. If you really want to go that route on generalizing into an analogy, a better one would be cargo shipping. For sure flights are great, but costly and may not fit well in many scenarios. As so, that's why we still have maritime and road transport.
Forget SOLID, go for this here new set of principles:
Cohesion - Keep related functionality tightly bound within components or classes.
Understanding - Code should be easily understandable by anyone on the team. Avoid convoluted logic, and write clean, well-commented code that others can follow.
Necessity - Build only what is required for the problem at hand.
Testability - Code should be designed with testing in mind, allowing for easy unit and integration tests.
Scalability - Architecture and codebase can grow with the application's needs.
@22:00
LSP is about polymorphism
Prime continues to not understand LSP
and it's not even an exclusively OOP thing
I just found this channel after Chris Lattner and I love listening to him toy around with these topics
Imagine getting credit for just creating an acronym for someone elses work. Its like the Maclaurin series in math.
That article got me! Since it was giving specific examples from the same project, when it came to "L" I was on the edge of my seat. I know this project doesn't use squares. I'm going to see a new "L" example. But NOOO! I think of all the children, about to make their Square's inherit from Rectangle, being saved by the "L" in solid.
You are mostly working on code you wrote. At least in your streams.
The vast majority of real world code I had to deal with is underabstracted, not overabstracted.
Classes having 1000+ lines being a common thing, very low cohesion.
You can't see "everything that happens within a single screen" like that.
I feel SOLID applies well at the overall class design level, but it starts to become a problem if you need to defer to these principals, line by line or property by property of a class. Take OCP. The first thing you need a clear understanding of is if, by design, you want the class to be extensible. If the answer is no, then relax the need for abstractions. SOLID needs to be applied in the context of an architecture, where you really want to focus on these principals where "parts" of your architecture interconnect, but within...you can afford to be a bit fast and furious with how you code things. And if you're wrong, then refactor.
FPOOP 🤣🤣🤣🤣🤣🤣🤣
"Every time you have have the hammer of inheritance, you end up getting nailed."
i wanna replace my "live laugh love" poster with that!
SOLID forces you to do "premature abstraction"
SOLID is a refactoring target, not a design language
SOLID is more about reducing dependencies so that changes are less expensive in the future
Solid is a guideline, it's not something that you should adhere to to the letter as you're writing your code. It should be something that you work towards but not something that you start with.
You don't disagree with it. You just don't agree it needs to be done all the time. The take away is there's a right time to do SOLID. Let complexity emerge and then refactor as necessary unless you know exactly what and how complexity will emerge and SOLID principle just naturally shows as you code like you know exactly what you need to do.
I've dealt with large projects and one way to tame complexities is to adhere to SOLID principles. In my 20 years of experience in coding, if the file is going beyond 400 lines, you should start rethinking about how you would want to break things apart. You will not wait for it to get to thousands of lines before doing that especially if your team has lot of juniors. Adhering to SOLID mitigates files from growing to thousands of lines.
Fun fact, places where I've worked that went hard on SOLID had just as buggy programs as those that didnt prioritize it.
That has nothing to do with SOLID.
I think that SOLID is flawed in a similar way to "Clean Code" or Database-Normlization.
It is subjective and, opposite to its goal, can make things harder to read, understand, maintain and perform.
Knowing about SOLID is important. Following it to an almost religious degree is not.
Knowing about SOLID is important simply to understand what it tries to solve (e.g. Prime still seems to have no idea about Liskov's), and understanding when to apply the principles.
Imagine a vector or matrix library with SOLID principles. They will literally want you to be making a whole file and class for each little thing you can do with a vector. This is my class that adds vectors, this is my class that computes the distance between vectors, etc etc. SOLID does one thing very well, spread your code so thin that it's actually not possible to understand what anything is actually doing.
SOLID says nothing about files. As far as your example, it really depends on whether those operations are behavior or not. For example, if you are making an application for vector math, where the user is in control of which operations to perform, then those operations would be behavior and might belong in separate classes, otherwise they are just operations on data.
edit: another reason to use objects/classes, and which separates them from simple data, is invariants you need to maintain
Basically what every object oriented programmer wants
It's not a violation of the Single Responsibility Principle to have all vector math functions in a single Vector class so as long as each of the functions can be used independently given that they are generic enough. Having Vector.Distance and Vector.Add in the same class and file for example is perfectly fine under SOLID.
Will watch later but here's my apriori take on SOLID:
I think it has some interesting things to say, but it'd probably be easier to communicate / remind about the principles if someone were to translate them into English.
EDIT:
HAHA! First line out of Prime's mouth: "I forget what they are all the time"
EDIT 2: Just noticed the article is different from the video title too 😄:
~"SOLID in plain English"
00:01:22 Robert J. Martin?? It's Robert C. Martin. Article credibility out the window right from the start. 🤦♂️
Thanks, fully agree. It's pretty wild to advocate for abstractions in a world where many coders can't even name a function properly, let alone prevent the name from being a straight lie after the third implementation change. Write a straight forward solution. Write it a few times. Get the functionality right. Get the performance right. Profile. Discover where abstractions can be of use and implement with care. Profile again. You know how to profile your project, right? Make conscious decisions based on the reality of your project about which actions and abstractions benefit your. specific. project. That's the only thing that matters. Also, a project driven by a single person will drastically differ from a codebase maintained by a thousand people. It most certainly did not enter this world looking like the latter, for very good and important reasons.
Locality of behaviour is just “I am too lazy to put functions into another file, just make code and don’t worry, because you are going to remake everything anyway”… except you won’t, and you will use this mess of giant files, with conflicts any time you touch the code, forever.