I'm more and more convinced as time goes on that domain modeling with discriminated unions and small, composable, immutable records that are only possible to create when all the data is present and valid is the absolute best of all worlds approach. Keep data and behavior separated, make illegal states unrepresentable, and compose most of your code in pure static functions acting upon that data that is guaranteed to be correct as enforced by the compiler. It's beautiful.
Thank you for the thoughtful feedback! You’re describing a beautifully disciplined approach to domain modeling, leveraging principles like type safety and functional purity. Keeping data and behavior separate, making illegal states unrepresentable, and relying on the compiler to enforce correctness is indeed a powerful way to create maintainable and robust systems. While it can add some upfront complexity, the long-term benefits are undeniable. Your perspective is both insightful and inspiring!
100% which is why I also mentioned structures/struct in the video What’s your opinion on say an address class which will format and work with a road address? Of course I think a Name class when there’s no processing to be done is way overkill I have to admit however I love the term you have used, mind if I use it in a future video? Looking forward to your response,
Ah yes, the "kingdom of nouns" antipattern! have a simple set of transformations for your data? Better have a class to oversee that! And a class for overseeing the overseeing class! And a class that is a factory for the overseer classes that will use reflection to call runtime code gen on in memory outputs of the AbstractFactoryMessagingHelperUtilityOverseerMaxiUtraZoomZoomServiceProvider to make sure we have dynamic allocation of the proper kubernetes clusters while the containers are being stitched together by a rogue AI drunk on power with a bad attitude and even worse discretion.. Ok.. Maybe I spun out a bit there. Lol.
Haha, you definitely spun out there, but I have to admit, I felt that! Nothing quite captures the ‘kingdom of nouns’ antipattern like a vivid, chaotic metaphor involving rogue AIs and Kubernetes clusters. You’re spot on though-over-engineering can lead to a forest of abstractions that’s impossible to navigate, let alone maintain. The challenge is always finding the sweet spot: enough structure to keep the code sane, but not so much that you need a class to manage the coffee machine’s lifecycle. Thanks for the laugh and the sharp insight! This might have to make it into a video as the perfect example of what not to do. If only I could stay serious for the entirety of the video, maybe I'll give it a shot.
Once you combine a data and logic into a class, you create a new type of entity, a structure, that is not related to the problem you're trying to solve. And now you have the same kind of a problem but fron the different spectrum.
That’s a fair observation, and it highlights the importance of balance in software design. Over-structuring can lead to unnecessary complexity, just as primitive obsession leads to fragility. The goal isn’t to create types for every possible abstraction but rather to encapsulate behaviors and logic that directly align with the domain. By doing so, we focus on making the code expressive, maintainable, and resistant to bugs while avoiding over-engineering. Good design is about finding that sweet spot where types enhance clarity without becoming the problem themselves.
With all respect, you're an old-school OOP programmer, and the basic premise of what you're saying is: Everything is an object, therefore always create a class for absolutely everything. There are many voices that saying this is not very good way to code. For me, there are two issues with this approach. The first is performance. OOP has more overhead and higher memory consumption. From my measurements, the biggest friction occurs when one class passes data to another. The longer the chain, the slower the code. The second issue is excessive abstraction. I’ve often come across OOP code that was very hard to trace-where the data is coming from and what it contains. Sometimes, the naming was poor, but more often, the problem was dealing with method()->method()->method() chains, which were just too convoluted to make sense of. Is this a skills issue on my part? Maybe, or maybe not. I’ve encountered cases where multiple experienced programmers struggled to understand such code. So, my approach is to use OOP only when it makes sense. For example, if it’s $user, then yes, it should be an object. If it’s $order, of course. But using a class for something like displaying an order status-when a simple function would suffice-feels like unnecessary OOP to me. When it comes to handling API requests, for instance, I do use OOP. However, I prefer a single class with multiple properties and constants, mainly to maintain a shared state across all methods, rather than overcomplicating the design. Maybe you can share some though about "everything must be object" or I interpret it wrong and explain, when not use OOP and when is absolute must. Thank you!
Thank you for such a thoughtful and well-articulated comment-I really appreciate the time you took to share your perspective, and I actually agree with many of your points. In the video, I deliberately take a more exaggerated approach, which might come across as “my way or the highway.” This is intentional and rooted in the philosophy I follow, similar to what martial arts teachers often do-exaggerating movements in training so students get it just right when it matters. In our trade, I believe it’s crucial to think critically and choose the right approach for each problem, rather than defaulting to our comfort zones. I’ve often seen the “primitives-only” mindset lead to overly complex workarounds, which can introduce more problems than they solve. While a simple string might suffice for something like storing a name, if you find yourself implementing convoluted logic to make primitives work, then this advice and the video’s exaggerated point absolutely apply. Thanks again for your comment-it adds a lot to the discussion!
Type safety in many cases is a cost borne at compile time rather than at runtime, and the idea that using primitives is more performant is really not true with modern optimising compilers. The move away from primitives has also mostly been driven by functional programming schools than OOP adherents.
That’s a valid concern, and it’s why thoughtful design is critical. Encapsulation should simplify understanding, not obscure it. If you’re jumping through 17 files, the issue isn’t the use of structured types-it’s poor cohesion and scattering of logic. A well-designed system keeps related logic together, making it clear where ‘2 + 2’ happens. The key is ensuring abstractions serve clarity and maintainability, not complexity for its own sake.
@@mvargasmoran Ah yes, sounds like we’ve both walked through the land of AbstractFactoryMessagingHelperUtilityOverseerMaxiUltraZoomZoom and lived to tell the tale-feeling just enough pain to survive, but also enough pain to learn to never, ever go back there again! It’s like a rite of passage in software development, emerging from the ‘kingdom of nouns’ with a renewed appreciation for simplicity. By the way, I’m collecting horror stories like this for some upcoming videos! I’ll be adding CTO insights to dissect the chaos, share lessons learned, and maybe sprinkle in some laughs. If all goes well, those should be live in the next few weeks. Thanks for the great reminder of why we strive for clarity and sanity in code!
Haskell certainly enforces a level of type discipline that makes primitive obsession nearly impossible, but for most teams, adopting it might introduce other challenges like a steeper learning curve or integration issues. The goal is to strike a balance between leveraging tools and languages that fit the team’s expertise while adopting practices-like reducing primitive obsession-that improve code quality across the board, no matter the language.
I'm more and more convinced as time goes on that domain modeling with discriminated unions and small, composable, immutable records that are only possible to create when all the data is present and valid is the absolute best of all worlds approach. Keep data and behavior separated, make illegal states unrepresentable, and compose most of your code in pure static functions acting upon that data that is guaranteed to be correct as enforced by the compiler. It's beautiful.
Thank you for the thoughtful feedback! You’re describing a beautifully disciplined approach to domain modeling, leveraging principles like type safety and functional purity. Keeping data and behavior separate, making illegal states unrepresentable, and relying on the compiler to enforce correctness is indeed a powerful way to create maintainable and robust systems. While it can add some upfront complexity, the long-term benefits are undeniable. Your perspective is both insightful and inspiring!
Class obsession is also a thing, and it's a way more prevalent problem.
100% which is why I also mentioned structures/struct in the video
What’s your opinion on say an address class which will format and work with a road address?
Of course I think a Name class when there’s no processing to be done is way overkill
I have to admit however I love the term you have used, mind if I use it in a future video?
Looking forward to your response,
@theseriouscto feel free!
Ah yes, the "kingdom of nouns" antipattern! have a simple set of transformations for your data? Better have a class to oversee that! And a class for overseeing the overseeing class! And a class that is a factory for the overseer classes that will use reflection to call runtime code gen on in memory outputs of the AbstractFactoryMessagingHelperUtilityOverseerMaxiUtraZoomZoomServiceProvider to make sure we have dynamic allocation of the proper kubernetes clusters while the containers are being stitched together by a rogue AI drunk on power with a bad attitude and even worse discretion.. Ok.. Maybe I spun out a bit there. Lol.
Haha, you definitely spun out there, but I have to admit, I felt that! Nothing quite captures the ‘kingdom of nouns’ antipattern like a vivid, chaotic metaphor involving rogue AIs and Kubernetes clusters.
You’re spot on though-over-engineering can lead to a forest of abstractions that’s impossible to navigate, let alone maintain. The challenge is always finding the sweet spot: enough structure to keep the code sane, but not so much that you need a class to manage the coffee machine’s lifecycle.
Thanks for the laugh and the sharp insight! This might have to make it into a video as the perfect example of what not to do. If only I could stay serious for the entirety of the video, maybe I'll give it a shot.
Once you combine a data and logic into a class, you create a new type of entity, a structure, that is not related to the problem you're trying to solve. And now you have the same kind of a problem but fron the different spectrum.
That’s a fair observation, and it highlights the importance of balance in software design. Over-structuring can lead to unnecessary complexity, just as primitive obsession leads to fragility. The goal isn’t to create types for every possible abstraction but rather to encapsulate behaviors and logic that directly align with the domain. By doing so, we focus on making the code expressive, maintainable, and resistant to bugs while avoiding over-engineering. Good design is about finding that sweet spot where types enhance clarity without becoming the problem themselves.
With all respect, you're an old-school OOP programmer, and the basic premise of what you're saying is: Everything is an object, therefore always create a class for absolutely everything.
There are many voices that saying this is not very good way to code. For me, there are two issues with this approach. The first is performance. OOP has more overhead and higher memory consumption. From my measurements, the biggest friction occurs when one class passes data to another. The longer the chain, the slower the code.
The second issue is excessive abstraction. I’ve often come across OOP code that was very hard to trace-where the data is coming from and what it contains. Sometimes, the naming was poor, but more often, the problem was dealing with method()->method()->method() chains, which were just too convoluted to make sense of. Is this a skills issue on my part? Maybe, or maybe not. I’ve encountered cases where multiple experienced programmers struggled to understand such code.
So, my approach is to use OOP only when it makes sense. For example, if it’s $user, then yes, it should be an object. If it’s $order, of course. But using a class for something like displaying an order status-when a simple function would suffice-feels like unnecessary OOP to me.
When it comes to handling API requests, for instance, I do use OOP. However, I prefer a single class with multiple properties and constants, mainly to maintain a shared state across all methods, rather than overcomplicating the design.
Maybe you can share some though about "everything must be object" or I interpret it wrong and explain, when not use OOP and when is absolute must. Thank you!
Thank you for such a thoughtful and well-articulated comment-I really appreciate the time you took to share your perspective, and I actually agree with many of your points.
In the video, I deliberately take a more exaggerated approach, which might come across as “my way or the highway.” This is intentional and rooted in the philosophy I follow, similar to what martial arts teachers often do-exaggerating movements in training so students get it just right when it matters.
In our trade, I believe it’s crucial to think critically and choose the right approach for each problem, rather than defaulting to our comfort zones. I’ve often seen the “primitives-only” mindset lead to overly complex workarounds, which can introduce more problems than they solve. While a simple string might suffice for something like storing a name, if you find yourself implementing convoluted logic to make primitives work, then this advice and the video’s exaggerated point absolutely apply.
Thanks again for your comment-it adds a lot to the discussion!
Type safety in many cases is a cost borne at compile time rather than at runtime, and the idea that using primitives is more performant is really not true with modern optimising compilers. The move away from primitives has also mostly been driven by functional programming schools than OOP adherents.
good video, thank you for sharing this
Did you subscribe? Thanks for the positive feedback!
@@theseriouscto yes! I look forward to more of your pov
I also hate when this "fix" becomes obsession and you jump around 17 files looking for a where the 2 + 2 actually happens.
That’s a valid concern, and it’s why thoughtful design is critical. Encapsulation should simplify understanding, not obscure it. If you’re jumping through 17 files, the issue isn’t the use of structured types-it’s poor cohesion and scattering of logic. A well-designed system keeps related logic together, making it clear where ‘2 + 2’ happens. The key is ensuring abstractions serve clarity and maintainability, not complexity for its own sake.
@@theseriouscto well, I can see that both of us have witnessed that mess.
@@mvargasmoran Ah yes, sounds like we’ve both walked through the land of AbstractFactoryMessagingHelperUtilityOverseerMaxiUltraZoomZoom and lived to tell the tale-feeling just enough pain to survive, but also enough pain to learn to never, ever go back there again! It’s like a rite of passage in software development, emerging from the ‘kingdom of nouns’ with a renewed appreciation for simplicity.
By the way, I’m collecting horror stories like this for some upcoming videos! I’ll be adding CTO insights to dissect the chaos, share lessons learned, and maybe sprinkle in some laughs. If all goes well, those should be live in the next few weeks. Thanks for the great reminder of why we strive for clarity and sanity in code!
Just rewrite it in Haskell.
Haskell certainly enforces a level of type discipline that makes primitive obsession nearly impossible, but for most teams, adopting it might introduce other challenges like a steeper learning curve or integration issues. The goal is to strike a balance between leveraging tools and languages that fit the team’s expertise while adopting practices-like reducing primitive obsession-that improve code quality across the board, no matter the language.