I have a use case to make updates to a slightly complex JSON object and then return the list of changes made (not the updated data). The process of finding the list of changes is extremely similar to the process of making the changes and it would bring in some redundancy if I chose to separate the two methods. So I'm wondering if there is a way to do this without choosing DRY over CQS and SRP.
Not all Command Query Separation (CQS) violations are created equal.
A famous example is popping a stack. The existence of a
pop command that mutates a stack while returning what was popped violates the clear separation between command and query. However, while many argue that to follow CQS,
pop can't return, I'll argue that there is a deeper violation here. One that can be mitigated without messing with
The real problem is if I have to write code that pops the stack and then pushes what I popped right back on the stack. You can't fix that by changing
pop. You fix that by creating
So long as
peek exists the most critical thing CQS asks for isn't being violated. Which is: a way to query without mutating state. If
peek gives you exactly what
pop gives you then that most critical part of CQS hasn't been violated.
Purists will complain that
pop still shouldn't return under CQS but I'm not one of them. So long as
peek is reasonably easy to find in the documentation of
pop this shouldn't cause problems. This is how I prefer to follow CQS. Never force users to mutate to query. Following that has a big payoff. Never allowing a command to double as a query only pays off because it forces people to go find the query and use that. Meh. Either way, give me
Now all that said, who said you even had to return?
What I see you doing is parsing and mutating a JSON data structure while logging your activity. Do that and you aint returning a thing. You've just expanded what's being mutated. Simple.
There would be no DRY violation here because we didn't duplicate parsing code for finding and making changes. We just made the code say more when run.
Don't want to put it in a log file? Need to show it to the user? Fine, pass the algorithm some way to send output to the user. Use it the same way you'd use a log. The algorithm doesn't have to know which it's talking to. It just needs an interface to talk through as it declares changes are being made. That separation respects SRP.
None of this involves creating a separate process for finding the list of changes and the process of making the changes. It's just about what you change.
You can take this even further and fold both the JSON and the changes together so that you can build the JSON from the record of every change ever made to it. That's not a new idea. Its how git works.
And even if you were forced to separate the process of finding the list of changes and the process of making changes that still doesn't force you to violate DRY. The process of making changes needs to know where those changes should be. The process of finding them does as well. So make one method that finds the changes and two others: one that makes them and one that records them.
Do that and it's easy to write a query that finds where changes would be made without making them because you separated finding and making. Just plug in changing code that makes no changes and you have a query that doesn't mutate.
Now sometimes DRY needs a little violating.
x = 3; y = 3; might seem repetitive but x and y mean different things. I don't care that right now they happen to have the same value. I'm keeping them separate so future programmers can change them independently. It's the same with repetitive code. Doesn't matter if every keystroke is the same. What matters is if that identical code exists so it can be changed independently. This is only a problem if a change in one always implies a change in the other.