You can repeat yourself by writing the same code for different business tasks, but you definitely should not solve the same business task using different code. This is my point of DRY principle
I think the way DRY is typically used in practice is as if it said "code should depend on existing code as much as possible" or " avoid writing new code when possible" As developers sometimes we bend over backwards to avoid writing new code, we see it when maintaining things, and when adding features to an existing code base. We bend over backwards and add unnecessary abstractions into code to try to avoid writing something new, or for the sake of consistency. DRY was never intended to mean any of this. I see it more as each piece of logic has a specific place where it belongs in a system, so identify that place and put it there. As you say some of that logic can be duplicated if it's used in different contexts. There are instances of dry that most developers can agree on that are typically framework types of code. We shouldn't have to write a new web framework every time we want to handle an http request. The challenge is when trying to apply DRY to business logic, because we don't often see the higher level boundaries and treat the small things as if they were a larger dependent component (like a web framework) without realizing it.
My previous job was doing the exact opposite of DRY. Like we were duplicating most things, using deeply nested blocks of code, and avoiding Abstraction as much as possible, even using globals as 'singletons'. The first time I saw the codebase I got sick to my stomach. But you know what? Working in it was not nearly as bad as I thought it would be. In fact, I enjoyed working in that codebase a lot because I could change whole sections of the code without worrying too much about unintended 'side effects'. Moreover, the minimal use of Abstraction really helped with debugging because I could follow most of the logic as a single coherent thread most times. Granted, it also had its downsides especially when it came to readability. But you eventually get used to collapsing large blocks of code and using search to navigate the codebase. I now work in a much more DRY and SOLID codebase and boy ooo boy do I hate it every second of it!
Here's my 2 cents - If it's boilerplate or setup code that's exactly the same across multiple cases or contexts. Don't repeat it. - If it's code for a non-business-related task that is straightforward that it's hardly ever going to change in the future (e.g. http requests, unit conversions). Don't repeat it. - If it's code that pertains to a business case or task that serves a given purpose in the system. Don't repeat it. - Finally, for example the unit conversion example. Don't add formatting logic to the unit conversion logic you have abstracted. Add the formatting logic where the unit conversion logic is being used not where it's being defined.
This is another good entry in your "it depends" -- coding rules are never really rules -- series of videos. I would probably try to think of a better example than the km conversion though as others have pointed out that it doesn't really ring true. I also wonder if it's worth having a little health warning on some of these that the material isn't for new players, although I doubt that would delay the day I have a junior coder insist that DRY is nonsense because they saw it on a YT video.
"Context is King" is my version of "it depends". Everything has trade-offs but if you don't know what they are, then it's pretty hard to say "it depends" because you need to follow it up with "it depends on X and Y then Z, otherwise maybe Q"
DRY - good in usually 80% cases, helpful to concentrate logic in proper places, not let it scatter without any reason. OK in the 10%, and perhaps damaging and too stiff in the last 10% of cases (very specific, highly recommend to examine case-by-case). Thats my CodeOpinion for today ;) Thanks Derek
The KM example I would never do the number of decimal places in a unit conversion like that, miles.toKM should return the most accurate (as possible for floating point numbers), that's not an issue with DRY but just an issue of formatting state in a function that shouldn't. Similar to if you had a database call that instead of returning the raw entity; returned an API DTO. In such a case of converting I would have that conversion defined as some compile time constant in the Miles & KM classes e.g. MilesPerKilometer and KilometersPerMile, those are never likely to change (more of a magic strings than DRY). If there's a common formatting i.e. expecting 2 decimal places rounded in a certain way (although I wouldn't bother with this example due to it only being a single function call anyway, an abstraction here would save no time); I would write that as a separate function if it's being used in a couple or more places (or if I expect it to be used in several places). I once worked on a codebase where DRY wasn't used very well; we were returning the same type of DTO in multiple places using the same type of entity as input. Each time he manually mapped it at the point of sending the API response which meant when the DTO changed to reflect the customer's request, I had to make the change in each occurrence of that mapping. I decided to instead make a mapping method that took the entity as input and returned the DTO (very common thing to do). I often say that if I need to write the same code 2 or more times it should be in it's own subroutine. I use a lot of templates & generics (depending on language I'm using) and lots of composition. I'm not sure if that's something you're advising against when referencing DRY applied to state vs DRY applied to routines & functions. I don't think that this really has anything to do with DRY so much as it has to do with coupling. You can use the DRY principle with state or with state modifying functions. I personally think DRY and coupling are 2 entirely unrelated issues.
I had a developer actively looking through the whole code base to find simular lines and putting them together in vlasses that made no sense whatsoever. I had a developer saying that because 3 out of 15 event subscriptions had the same code, we needed an abstract class for the 15 event subscription with that code and all 25 should use it. That is the issue with DRY: nobody understands what it means, its just applied blandly. Maintainability it not "in how many plavrs do i have to change this code in the future", its "how easy is to change the shape of the existing code". Which also nobody seems to understand. Nevermind simplicity, ease of acces to workflow-wide informatio, and different reasons for change.
I understand the idea behind DRY and it makes good sense...in a system that never changes. Unfortunately, as developers we usually live in a world where the systems we work on are in a constant state of change. Features are constantly changed as the people in charge get a "Oh btw, can we have the system also do (insert whatever requirement you want here)" and if you have a lot of entities and function calls that are shared between numerous features, you risk the whole system come tumbling down because you changed a small bit of logic in a function that 30+ features depends on and you didn't fully understand the ramifications of what you did. These days, I mostly use vertical slice architecture and separate my code into features, where the logic never touch other features. It leads to some code duplication, but its a small price to pay for not having to worry about changing one feature completely messing up the application and causing data corruption. The only place I tend to use DRY these days is with helper functions that are simple and mostly for the sake of the developers, so decoupled from the features in that the outcome of the features don't really depend on what the helper methods do.
I think that DRY is one of the most important principles in writing good, maintainable code. Of course, as long as you know where to use it and have a clear indication of what should be abstracted. Sure, it is not a trivial task, but in many cases it is pretty clear. Tests are your strongest ally in this task.
What I really hate on software development is that it is kind of twilight zone. We tend to develop standards but constantly dispute on them. One is jealous for following some standard, another one is strongly against it. Everything has pros and cons. Everything has it's cost. It depends. So after 10 years being software developer my honest answer to question "How should we develop programs?" would be "I don't fckn know". But there is one thing I know: AI doesn't give a fck on it all and will deliver to customers the software that works as they expect and no one will care what the hell is going on under the cover.
Also 95% of what we do is just CRUD but there is 1000 and one receipts of doing this, and everyone is sure that his boilerplate is the best boilerplate. Not having clear standard on transmitting 1.5 fields from html page into database is just heartbreaking.
I'm constantly advocating against CRUD where it isn't appropriate. CRUD exists and has utility, and I agree that 95% of what is being developed is CRUD. However, that's not to say that 95% of it should be.
For a long time I thought that DRY was the most important principle and good design followed from it. Boy, was I wrong. 🤦♂Luckily, I realized that a while ago.
Good. To illustrate the various approach, but to understand the better one, will give an example, and please try to explain with real world eg., by making a video on it. Putting it simply.. Business part: Request Entity Org and user entities Having a workflow service, that creates the tasks for the users for any requests to approve or reject.
so would you have some 'base' vehicle reference with like an ID, VIN, make/model, whatever, and then specific things like a VehicleDispatch table that references the vehicle table, and adds all its specific data? And then a TrailerUnhook that references a trailer, a vehicle, and all its specific business logic? I feel like the high level examples are good if you're used to designs like this, but I feel like i need some implementation details to fully understand the examples. Even just a mini ERD
Yes, you would likely have the vehicle details (vin, make, model) as one model. Transactional operations for things like hooking/unhooking a trailer are likely another model in a totally different boundary. Dig around more of my videos and you'll probably get a better sense. You'll start seeing how CQRS and Vertical Slices come into play.
I'm not sure about your example with the Kilometers abstraction. You let the impression having and using that kind of shared abstraction for unit conversion and manipulation across the system is bad, which it is not. It can be in an utility library and it's perfectly fine to use it across the system, you actually should, and it should be DRY. You could still have the precision problem even if you're using it only in some specific parts of your system. The issue is with the design of the class, not that it should not be DRY.
From what I see, it's not as natural as you think. What's seemingly more natural for people is to tend to think of a singular concept that spans multiple contexts.
You can repeat yourself by writing the same code for different business tasks, but you definitely should not solve the same business task using different code. This is my point of DRY principle
What abort the day you want to change something because it actually needs to change and not just add new business logic? Good luck with your globals.
I think the way DRY is typically used in practice is as if it said "code should depend on existing code as much as possible" or " avoid writing new code when possible" As developers sometimes we bend over backwards to avoid writing new code, we see it when maintaining things, and when adding features to an existing code base. We bend over backwards and add unnecessary abstractions into code to try to avoid writing something new, or for the sake of consistency.
DRY was never intended to mean any of this. I see it more as each piece of logic has a specific place where it belongs in a system, so identify that place and put it there. As you say some of that logic can be duplicated if it's used in different contexts. There are instances of dry that most developers can agree on that are typically framework types of code. We shouldn't have to write a new web framework every time we want to handle an http request. The challenge is when trying to apply DRY to business logic, because we don't often see the higher level boundaries and treat the small things as if they were a larger dependent component (like a web framework) without realizing it.
What was intended and what in practice happens is one of our industries greatest constant failures. Say hi to Agile, REST, DevOps...
My previous job was doing the exact opposite of DRY. Like we were duplicating most things, using deeply nested blocks of code, and avoiding Abstraction as much as possible, even using globals as 'singletons'. The first time I saw the codebase I got sick to my stomach. But you know what? Working in it was not nearly as bad as I thought it would be. In fact, I enjoyed working in that codebase a lot because I could change whole sections of the code without worrying too much about unintended 'side effects'. Moreover, the minimal use of Abstraction really helped with debugging because I could follow most of the logic as a single coherent thread most times. Granted, it also had its downsides especially when it came to readability. But you eventually get used to collapsing large blocks of code and using search to navigate the codebase. I now work in a much more DRY and SOLID codebase and boy ooo boy do I hate it every second of it!
Indirection and coupling have their utility, but they also can make things more complex.
Here's my 2 cents
- If it's boilerplate or setup code that's exactly the same across multiple cases or contexts. Don't repeat it.
- If it's code for a non-business-related task that is straightforward that it's hardly ever going to change in the future (e.g. http requests, unit conversions). Don't repeat it.
- If it's code that pertains to a business case or task that serves a given purpose in the system. Don't repeat it.
- Finally, for example the unit conversion example. Don't add formatting logic to the unit conversion logic you have abstracted. Add the formatting logic where the unit conversion logic is being used not where it's being defined.
This is another good entry in your "it depends" -- coding rules are never really rules -- series of videos. I would probably try to think of a better example than the km conversion though as others have pointed out that it doesn't really ring true. I also wonder if it's worth having a little health warning on some of these that the material isn't for new players, although I doubt that would delay the day I have a junior coder insist that DRY is nonsense because they saw it on a YT video.
"Context is King" is my version of "it depends". Everything has trade-offs but if you don't know what they are, then it's pretty hard to say "it depends" because you need to follow it up with "it depends on X and Y then Z, otherwise maybe Q"
DRY - good in usually 80% cases, helpful to concentrate logic in proper places, not let it scatter without any reason. OK in the 10%, and perhaps damaging and too stiff in the last 10% of cases (very specific, highly recommend to examine case-by-case). Thats my CodeOpinion for today ;) Thanks Derek
Everyone has CodeOpinion!
The KM example I would never do the number of decimal places in a unit conversion like that, miles.toKM should return the most accurate (as possible for floating point numbers), that's not an issue with DRY but just an issue of formatting state in a function that shouldn't. Similar to if you had a database call that instead of returning the raw entity; returned an API DTO.
In such a case of converting I would have that conversion defined as some compile time constant in the Miles & KM classes e.g. MilesPerKilometer and KilometersPerMile, those are never likely to change (more of a magic strings than DRY). If there's a common formatting i.e. expecting 2 decimal places rounded in a certain way (although I wouldn't bother with this example due to it only being a single function call anyway, an abstraction here would save no time); I would write that as a separate function if it's being used in a couple or more places (or if I expect it to be used in several places).
I once worked on a codebase where DRY wasn't used very well; we were returning the same type of DTO in multiple places using the same type of entity as input. Each time he manually mapped it at the point of sending the API response which meant when the DTO changed to reflect the customer's request, I had to make the change in each occurrence of that mapping. I decided to instead make a mapping method that took the entity as input and returned the DTO (very common thing to do). I often say that if I need to write the same code 2 or more times it should be in it's own subroutine.
I use a lot of templates & generics (depending on language I'm using) and lots of composition. I'm not sure if that's something you're advising against when referencing DRY applied to state vs DRY applied to routines & functions. I don't think that this really has anything to do with DRY so much as it has to do with coupling. You can use the DRY principle with state or with state modifying functions. I personally think DRY and coupling are 2 entirely unrelated issues.
I had a developer actively looking through the whole code base to find simular lines and putting them together in vlasses that made no sense whatsoever.
I had a developer saying that because 3 out of 15 event subscriptions had the same code, we needed an abstract class for the 15 event subscription with that code and all 25 should use it.
That is the issue with DRY: nobody understands what it means, its just applied blandly.
Maintainability it not "in how many plavrs do i have to change this code in the future", its "how easy is to change the shape of the existing code". Which also nobody seems to understand.
Nevermind simplicity, ease of acces to workflow-wide informatio, and different reasons for change.
I understand the idea behind DRY and it makes good sense...in a system that never changes.
Unfortunately, as developers we usually live in a world where the systems we work on are in a constant state of change. Features are constantly changed as the people in charge get a "Oh btw, can we have the system also do (insert whatever requirement you want here)" and if you have a lot of entities and function calls that are shared between numerous features, you risk the whole system come tumbling down because you changed a small bit of logic in a function that 30+ features depends on and you didn't fully understand the ramifications of what you did.
These days, I mostly use vertical slice architecture and separate my code into features, where the logic never touch other features. It leads to some code duplication, but its a small price to pay for not having to worry about changing one feature completely messing up the application and causing data corruption.
The only place I tend to use DRY these days is with helper functions that are simple and mostly for the sake of the developers, so decoupled from the features in that the outcome of the features don't really depend on what the helper methods do.
Love the title! Looking forward to watching the rest of this
3:54 INTEND YOUR PUNS!
you're right, it kind of was intended.. i take that back.
I think that DRY is one of the most important principles in writing good, maintainable code. Of course, as long as you know where to use it and have a clear indication of what should be abstracted. Sure, it is not a trivial task, but in many cases it is pretty clear. Tests are your strongest ally in this task.
Ironically the most uttered phrase in this video is “don’t repeat yourself”
😂
What I really hate on software development is that it is kind of twilight zone. We tend to develop standards but constantly dispute on them. One is jealous for following some standard, another one is strongly against it. Everything has pros and cons. Everything has it's cost. It depends. So after 10 years being software developer my honest answer to question "How should we develop programs?" would be "I don't fckn know". But there is one thing I know: AI doesn't give a fck on it all and will deliver to customers the software that works as they expect and no one will care what the hell is going on under the cover.
Also 95% of what we do is just CRUD but there is 1000 and one receipts of doing this, and everyone is sure that his boilerplate is the best boilerplate. Not having clear standard on transmitting 1.5 fields from html page into database is just heartbreaking.
I felt this. 18 years in and I feel like 75% of the things I've been taught is debatable. I'm tired of debating.
I'm constantly advocating against CRUD where it isn't appropriate. CRUD exists and has utility, and I agree that 95% of what is being developed is CRUD. However, that's not to say that 95% of it should be.
This channel is not just about systems design, it's more about common sense and critical thinking.
Thanks a lot
Interesting, never thought about it that way.
For a long time I thought that DRY was the most important principle and good design followed from it. Boy, was I wrong. 🤦♂Luckily, I realized that a while ago.
I do suspect most think of it as about characters/code though. Just my guess.
Good.
To illustrate the various approach, but to understand the better one, will give an example, and please try to explain with real world eg., by making a video on it.
Putting it simply..
Business part:
Request Entity
Org and user entities
Having a workflow service, that creates the tasks for the users for any requests to approve or reject.
so would you have some 'base' vehicle reference with like an ID, VIN, make/model, whatever, and then specific things like a VehicleDispatch table that references the vehicle table, and adds all its specific data? And then a TrailerUnhook that references a trailer, a vehicle, and all its specific business logic? I feel like the high level examples are good if you're used to designs like this, but I feel like i need some implementation details to fully understand the examples. Even just a mini ERD
Yes, you would likely have the vehicle details (vin, make, model) as one model. Transactional operations for things like hooking/unhooking a trailer are likely another model in a totally different boundary. Dig around more of my videos and you'll probably get a better sense. You'll start seeing how CQRS and Vertical Slices come into play.
I'm not sure about your example with the Kilometers abstraction. You let the impression having and using that kind of shared abstraction for unit conversion and manipulation across the system is bad, which it is not. It can be in an utility library and it's perfectly fine to use it across the system, you actually should, and it should be DRY. You could still have the precision problem even if you're using it only in some specific parts of your system. The issue is with the design of the class, not that it should not be DRY.
The intent, which I did not succeed for you (and that's ok) was not that it is bad or good (because context matters) but to acknowledge the coupling.
Seems easy, when the new functionality is needed in the conversion, just make a new function
This comes as a natural thought, aggregates and entities can be conceptually different in different contexts
From what I see, it's not as natural as you think. What's seemingly more natural for people is to tend to think of a singular concept that spans multiple contexts.
The presenter reminds me of Dr Cox from Scrubs and I don't know why.
That's a new one. Can't say I've been called that one yet. Usually Dirk Nowitzki
Let's do DDD with separated contexts but use one database and same tables
Your domain model isn't your data model.
just don't use Imperial, LOL
Derek, I think you repeated yourself a lot in this video!
Do I?
@@CodeOpinion Yes but to make a point.
Great video tho!