Since I have followed you, I have been applying that technique of exposing "What" and not "How". I must admit you have played essential role in improving my coding style, now I am proud of my code which is there in Production.
I am glad to hear that. Indeed, telling what will happen but encapsulating how - that is the critical enabler to writing flexible, adaptable code which is, in turn, the critical requirement in enterprise-grade applications.
IoC + interfaces + functional programming + c# 10/11 with no null + immutable objects with pure functions + SOLID == give me the strongest code without any bug except probably the logical ones. Functional programming with using LINQ provides much more readable code without loops if-else branches and as a result no bugs. Thanks to Zoran Horvat. I listened all your Pluralsight lectures. It is terrific.
Knowing a programming concept and how to effectively apply it is what draws out the learning process. Sometimes I feel like I've been using nails to hang pictures until you come along and demonstrate how to use them to build a house. Your practical and real-world examples make all the difference. Great video!
Excellent, I've used this style of programming years ago while I was using c# at my day job but have since forgotten as I'm using Python daily. Thank you for the video, much appreciated. Always good to see techniques from others.
People usually dont like this approach because they say it adds indirection and complexity and now they need to navigate away from the source to find where something is being done. Then we end up with methods doing too much and being really complex. Of course, we avoided indirection, but at what cost? I rather have a readable code then a clever one.
I understand what you are doing, but it does seam difficult to test because of all the static functions that are created. Could you do a video on how you would test with this workflow?
Each static function is tested in isolation, and that is a regular process. The Demo class which calls them is there only for the sake of this video - no such class would exist in a production project, and therefore it would not be tested.
Hello, Zoran, great video as always, I really appreciate your work as a developer! Don’t you mind to suggest some of your favorite books concerning system design and architecture that helped you gain your key knowledge that you are now sharing with us? 😊
Yes, it is easy to understand, but is it easy to debug without a unit test, absolutely not. I usualy make sure the return value of a method is obtainable simply by putting my mouse pointer on it, the format you use make it difficult to confirm the return value is right. I suppose unit tests are a prerequisite with this style and the most efficient way to debug this kind of style is : Don't debug, write a test to find where the bug is. It would be interesting to see how you debug this kind of application or, at least, have your opinion. Thank you for these excelent and interesting videos.
I see your point, but that doesn't happen in my way of coding. First of all, I am applying a strict incremental coding technique, with application running 100% of the time. Therefore, any bug I make must show up right away, and I must have made it in the very last line of code I wrote. When debugging complex portions of code, I rely on the console. After years of using debuggers, I settled back with the oldest of all styles. In case that you still want to use the debugger, I would say that debuggers should be improved. For instance, Visual Studio tracks the return value for you, and you can see it on method return with no special action. Other environments and debuggers don't have such a feature, so far as I know, but I'd rather say that is an omission on their behalf. In the long run, I expect C# to put more and more emphasis on mappings, i.e. methods that only return a value and do nothing else. We will be seeing expression-bodied methods more than ever. That urges for novel debugging methods and tools.
@@zoran-horvat I will never buzz off from your super clean explanations, concerning the C Sharp Language. Thanks again. Would love to talk with you. 🦄🍉🤭⚔️ That's for another time. Have a good day
tnx to the video! very interesting. But when you have many levels of some wrapping such as at video, your debugging is very hard. because the integrity of the understanding of what is happening is lost
It is true that debugging is somewhat harder when object composition is applied, though even debuggers are not what they used to be. For instance, the debugger in Visual Studio has been able to report the return value from a method years ago, giving wings to expression-bodied methods. But the greatest pitfall of only seeing the complexity where object composition is used is in failing to see that it is not a desire, but a necessity. One cannot implement a complex domain model, with hundreds of business rules, without variation built into objects. And that variation is achieved through composition of objects and composition of functions.
Because this is only one out of six examples that I know on top of my head in this very code base where the same capping logic applies, each time in the same way and each time in slightly different settings. There will never be two functions, as you have put it. It would be two times N, where N is arbitrarily large and could grow to two dozens in an enterprise-grade discounts engine - as I said, 6 at the moment in a stripped-down demo version, but could easily be dozens in a production version. I've been there more than once in my career, and I know price engines all to well. They are hands down the most complex pieces of any retail application. The way you have put it sounds like you prefer rampant repetition of the critical piece of the financial domain logic across the application, right?
Here's how OOP destroys itself, rediscovering functional programming all over again. I suppose it's a good thing, although it's only half-way there. But this example shows how functions are more readable than objects.
Functional programming is essentially a restriction upon object-oriented programming - once it matured to be applicable in business applications, rather than just in faculty exams. It was mainly accepted after understanding the errors of the past, made in object-oriented designs. However, functional programming is limited in its own ways. It is anything but the solution in itself. Knowing that, it shouldn't come as a surprise that the best designs are attained by combining the two methods - object-oriented and functional. Neither is complete without the other.
I would agree with @piotrkozbial8753. The example you shown is an example of flawness of OOP. You shown set of classes with IDiscount as a constructor argument and with a single method in the class. These classes are subjects to be converted to extension methods. Moreover, they can be private methods. It is not necessary to instantiate object if you need to call a single method only. Instantiation will cause 1) memory consumption 2) CPU wasting for memory allocation + constructor execution + garbage collection. Not so efficient approach, even if constructor execution is optimized by JIT compiler. I do agree with you @zoran-horvat, idea to express "what" instead of "how" is great! But the samples in this video are not the best choise. It looks like "we introduced a problem and now we will show how to heroically solve it" %) I do not agree with @piotrkozbial8753 that extension methods are "rediscovering" of functional(!) programming. People name it "procedural" programming. It is even available in C language %). Yes, sintax in C will be a bit different, but it is still element of "procedural" approach.
@@replicant9611 Many of my recent videos are on that theme, nibbling one little problem at a time. I'm not sure I can cover the topic at once, at any level of detail. The interconnectedness of all things between OOP and FP is massive.
It's bad Design IMO. Your code lacks cohesion when you're distributing all those little parts to a decorator. It's hard to reason about interactions between decorators. Strategy / decorator have their place when you mix data sources, put a caching layer on top, etc. It's really useful to abstract different ports and adapters. But in your case it's just business logic and the whole decorator construct could be a single 30 line function. So it's clearly overengineered.
@@marcotroster8247 I think you have missed the point. To find the point, do this example your way. P.S. I'll give you a hint. It's not a 30-line function. It's 70 30-line functions, including per-deployment functions.
@@zoran-horvat I don't mind 70 30 line implementations as long as each one fits on a screen and reads well. In case a decorator contains a bug, you'll encounter too much coupling to make any changes to the decorator directly unless there's thorough unit testing in place to cover all usages. You'll end up versioning the decorators and add duplication that way. You can't escape the duplication. So I'd suggest to focus on readability and ease of use.
@@marcotroster8247 That is 2,000 lines of copy & paste code instead of 100 lines of code with no repetition. As a development lead, I would never allow such code to be committed. P.S. If you found a bug in a decorator (how on earth can there be a bug in a one-liner?!) then you fix the bloody bug. If you copied and pasted that decorator's implementation into 70 places in the code base, then you have 70 bugs.
@@zoran-horvat Most likely I'd get the design right to save the 1900 lines. But I wouldn't trust my coworkers to model the discount decorators correctly. And I wouldn't trust them to maintain my code either. My complicated design is just a liability at that point despite the elegance. (sad truth) If it's 70 special discount rules, maybe you should figure out a way for users to define their special discount A/B test experiments and benchmark their sales success. Then the hard coded 1900 lines go away entirely and the discount decorators finally make sense as building blocks for discount rule compositions by the user. I see too many devs go with the decorator solution right away when there are only 2-3 special discounts. Architecture is very situational and should develop over time. Also 70 special discounts would be way above my threshold to still hard code this. The plumbing should be specified inside a config file or database table.
@@marcotroster8247 Not 70 rules. Less than ten rules and up to three combinators, generating dozens and hundreds of combinations. I cannot escape the feeling that you have missed the point by a continent. You simply cannot replace a set of rules with a combinatorial explosion of concrete implementations. Besides that, your opinion about decorators is beyond logic. I'd suggest you to reset and start that train of thought from scratch.
Since I have followed you, I have been applying that technique of exposing "What" and not "How". I must admit you have played essential role in improving my coding style, now I am proud of my code which is there in Production.
I am glad to hear that. Indeed, telling what will happen but encapsulating how - that is the critical enabler to writing flexible, adaptable code which is, in turn, the critical requirement in enterprise-grade applications.
IoC + interfaces + functional programming + c# 10/11 with no null + immutable objects with pure functions + SOLID == give me the strongest code without any bug except probably the logical ones. Functional programming with using LINQ provides much more readable code without loops if-else branches and as a result no bugs. Thanks to Zoran Horvat. I listened all your Pluralsight lectures. It is terrific.
Knowing a programming concept and how to effectively apply it is what draws out the learning process. Sometimes I feel like I've been using nails to hang pictures until you come along and demonstrate how to use them to build a house. Your practical and real-world examples make all the difference. Great video!
Thanks!
“I don’t like to think when I’m writing code.”
That should be on a tee shirt.
Agree.
Excellent, I've used this style of programming years ago while I was using c# at my day job but have since forgotten as I'm using Python daily.
Thank you for the video, much appreciated. Always good to see techniques from others.
Thanks!
People usually dont like this approach because they say it adds indirection and complexity and now they need to navigate away from the source to find where something is being done. Then we end up with methods doing too much and being really complex. Of course, we avoided indirection, but at what cost? I rather have a readable code then a clever one.
I understand what you are doing, but it does seam difficult to test because of all the static functions that are created. Could you do a video on how you would test with this workflow?
Each static function is tested in isolation, and that is a regular process. The Demo class which calls them is there only for the sake of this video - no such class would exist in a production project, and therefore it would not be tested.
Hello, Zoran, great video as always, I really appreciate your work as a developer! Don’t you mind to suggest some of your favorite books concerning system design and architecture that helped you gain your key knowledge that you are now sharing with us? 😊
Yes, it is easy to understand, but is it easy to debug without a unit test, absolutely not. I usualy make sure the return value of a method is obtainable simply by putting my mouse pointer on it, the format you use make it difficult to confirm the return value is right. I suppose unit tests are a prerequisite with this style and the most efficient way to debug this kind of style is : Don't debug, write a test to find where the bug is. It would be interesting to see how you debug this kind of application or, at least, have your opinion. Thank you for these excelent and interesting videos.
I see your point, but that doesn't happen in my way of coding. First of all, I am applying a strict incremental coding technique, with application running 100% of the time. Therefore, any bug I make must show up right away, and I must have made it in the very last line of code I wrote.
When debugging complex portions of code, I rely on the console. After years of using debuggers, I settled back with the oldest of all styles.
In case that you still want to use the debugger, I would say that debuggers should be improved. For instance, Visual Studio tracks the return value for you, and you can see it on method return with no special action. Other environments and debuggers don't have such a feature, so far as I know, but I'd rather say that is an omission on their behalf.
In the long run, I expect C# to put more and more emphasis on mappings, i.e. methods that only return a value and do nothing else. We will be seeing expression-bodied methods more than ever. That urges for novel debugging methods and tools.
This is beautiful :)
Hey Zoran. Love your explanation. But have a side question.
Did you ever manged to get the discount bigger then the price, 😂
No. Buzz off :)
@@zoran-horvat I will never buzz off from your super clean explanations, concerning the C Sharp Language. Thanks again. Would love to talk with you. 🦄🍉🤭⚔️ That's for another time. Have a good day
@@jonny.rubber I was kidding. Thank you for your support!
Discount > price == refund 😉
tnx to the video! very interesting. But when you have many levels of some wrapping such as at video, your debugging is very hard. because the integrity of the understanding of what is happening is lost
you need to be able to stop
It is true that debugging is somewhat harder when object composition is applied, though even debuggers are not what they used to be. For instance, the debugger in Visual Studio has been able to report the return value from a method years ago, giving wings to expression-bodied methods.
But the greatest pitfall of only seeing the complexity where object composition is used is in failing to see that it is not a desire, but a necessity. One cannot implement a complex domain model, with hundreds of business rules, without variation built into objects. And that variation is achieved through composition of objects and composition of functions.
What in the name of Jesus, why are there 200 factory proxy builder cappers instead of 2 functions.
Because this is only one out of six examples that I know on top of my head in this very code base where the same capping logic applies, each time in the same way and each time in slightly different settings.
There will never be two functions, as you have put it. It would be two times N, where N is arbitrarily large and could grow to two dozens in an enterprise-grade discounts engine - as I said, 6 at the moment in a stripped-down demo version, but could easily be dozens in a production version.
I've been there more than once in my career, and I know price engines all to well. They are hands down the most complex pieces of any retail application.
The way you have put it sounds like you prefer rampant repetition of the critical piece of the financial domain logic across the application, right?
It hasn't clicked yet
Watch again :)
@@zoran-horvat yes, will do. Thank you sir.
Here's how OOP destroys itself, rediscovering functional programming all over again. I suppose it's a good thing, although it's only half-way there. But this example shows how functions are more readable than objects.
Functional programming is essentially a restriction upon object-oriented programming - once it matured to be applicable in business applications, rather than just in faculty exams. It was mainly accepted after understanding the errors of the past, made in object-oriented designs. However, functional programming is limited in its own ways. It is anything but the solution in itself.
Knowing that, it shouldn't come as a surprise that the best designs are attained by combining the two methods - object-oriented and functional. Neither is complete without the other.
I would agree with @piotrkozbial8753. The example you shown is an example of flawness of OOP.
You shown set of classes with IDiscount as a constructor argument and with a single method in the class. These classes are subjects to be converted to extension methods. Moreover, they can be private methods.
It is not necessary to instantiate object if you need to call a single method only. Instantiation will cause 1) memory consumption 2) CPU wasting for memory allocation + constructor execution + garbage collection. Not so efficient approach, even if constructor execution is optimized by JIT compiler.
I do agree with you @zoran-horvat, idea to express "what" instead of "how" is great! But the samples in this video are not the best choise. It looks like "we introduced a problem and now we will show how to heroically solve it" %)
I do not agree with @piotrkozbial8753 that extension methods are "rediscovering" of functional(!) programming. People name it "procedural" programming. It is even available in C language %). Yes, sintax in C will be a bit different, but it is still element of "procedural" approach.
@@zoran-horvat Please, can you make a video about OOP and FP? And how would you make it complement each other.
@@replicant9611 Many of my recent videos are on that theme, nibbling one little problem at a time. I'm not sure I can cover the topic at once, at any level of detail. The interconnectedness of all things between OOP and FP is massive.
Like as almost everything in the world, the best results are obtained by combining multiple sources
It's bad Design IMO. Your code lacks cohesion when you're distributing all those little parts to a decorator. It's hard to reason about interactions between decorators.
Strategy / decorator have their place when you mix data sources, put a caching layer on top, etc. It's really useful to abstract different ports and adapters.
But in your case it's just business logic and the whole decorator construct could be a single 30 line function. So it's clearly overengineered.
@@marcotroster8247 I think you have missed the point. To find the point, do this example your way.
P.S. I'll give you a hint. It's not a 30-line function. It's 70 30-line functions, including per-deployment functions.
@@zoran-horvat I don't mind 70 30 line implementations as long as each one fits on a screen and reads well.
In case a decorator contains a bug, you'll encounter too much coupling to make any changes to the decorator directly unless there's thorough unit testing in place to cover all usages. You'll end up versioning the decorators and add duplication that way. You can't escape the duplication. So I'd suggest to focus on readability and ease of use.
@@marcotroster8247 That is 2,000 lines of copy & paste code instead of 100 lines of code with no repetition.
As a development lead, I would never allow such code to be committed.
P.S. If you found a bug in a decorator (how on earth can there be a bug in a one-liner?!) then you fix the bloody bug. If you copied and pasted that decorator's implementation into 70 places in the code base, then you have 70 bugs.
@@zoran-horvat Most likely I'd get the design right to save the 1900 lines. But I wouldn't trust my coworkers to model the discount decorators correctly. And I wouldn't trust them to maintain my code either. My complicated design is just a liability at that point despite the elegance. (sad truth)
If it's 70 special discount rules, maybe you should figure out a way for users to define their special discount A/B test experiments and benchmark their sales success. Then the hard coded 1900 lines go away entirely and the discount decorators finally make sense as building blocks for discount rule compositions by the user.
I see too many devs go with the decorator solution right away when there are only 2-3 special discounts. Architecture is very situational and should develop over time. Also 70 special discounts would be way above my threshold to still hard code this. The plumbing should be specified inside a config file or database table.
@@marcotroster8247 Not 70 rules. Less than ten rules and up to three combinators, generating dozens and hundreds of combinations.
I cannot escape the feeling that you have missed the point by a continent. You simply cannot replace a set of rules with a combinatorial explosion of concrete implementations.
Besides that, your opinion about decorators is beyond logic.
I'd suggest you to reset and start that train of thought from scratch.