The short answer
I expect that the predominant consensus to this issue is going to be in favor of separation as much as possible.
While I absolutely agree with the underlying intention of this consensus, I somewhat disagree on the degree to which it is (almost blindly) applied as a blanket rule. The rest of this answer focuses on the parts I disagree with, because that part of me also agrees with your question's assertion that excessive separation itself can start to feel dirty as well.
That being said, I am not arguing against separation. I am merely arguing against the blind application of any blanket rule.
Separation vs reusability
At the outset, I agree with you. I am not a fan of repeating the same thing over and over. I see many respected colleagues advocate for keeping everything separated, but I also see it happen to a degree where my job consists more of copy/pasting than anything else, which is the antithesis of reusability.
As I am typing this, I am in the middle of copy paste adding a new feature and I've literally created an entire enterprise feature (7 projects, including unit and integration tests) by copying files and replacing any mention of
But I do understand why it is generally recommended. Just because you may have two viewmodels now which happen to have the same properties/behavior, does not mean that these viewmodels will stay that way forever.
When you get to the point where you need to change one of the viewmodels, you notice the benefit of already having split everything:
- If you had already created two separate viewmodels, you can simply adjust the one you need to adjust.
- If you had not already created two separate viewmodels, you are now going to have to spend effort to separate them, after which you can start adjusting the viewmodel you needed to adjust.
Programmers tend to forget things over time, and if you did (or didn't) separate the viewmodels months before during setup, you may have forgotten parts of the application structure by now, which means that when you have to separate your viewmodels (now), you might struggle in making sure that you haven't forgotten anything anywhere.
The above paragraph clearly favors early separation, as it means you are performing the separation task at the time of setting everything up, which is the point in time where you have the best working knowledge (as opposed to the future where you will have forgotten things).
Gambling on future changes
It's essentially a guessing game: will these viewmodels grow apart over time or not?
In either case, if you end up being right, you put in the right amount of effort. No argument there. But when you're wrong, the consequences of being wrong are not comparable:
- If you needed to separate the viewmodels and didn't do it; the consequence is that you have to wire and additional model, which can be a complex task.
- If you never needed to separate the viewmodels but you did it anyway; the consequence is that you lost a fraction of development time copy/pasting two files.
The math is clear here. It's better to err on the side of separation because the consequence of being wrong is less time consuming than if you hadn't separated and were wrong.
But, and this is an important but that I feel many developers forget:
- Separating everything means that you do this for everything (100%), as it is a blanket approach.
- Not separating everything, and only separating them once you need to, can be done on a case-by-case basis (<100%).
If you only end up having to (late) separate 1% of your classes in the future, as opposed to having to (early) separate 100% of your classes today, that means that the math changes.
Early separation is clearly quicker than late separation. However, 100 early separations are probably not going to be quicker than 1 late separation.
The better choice here is the one with the least development time (note: do take code complexity into account here as it impacts the future development time).
There is no universally correct answer here. It very much depends on the complexity of your codebase and the odds of your expectation (needing to make future changes) being wrong.
There are a ton of precedents for developers cutting corners and ending up with an unmaintainable mess of a codebase. It's a very common thing, and it's also the reason why good practice pressure on developers is so high. This is also the root of the "pro separation" argument: preventing unmaintainable messes and long development time on future maintenance.
However, erring too much the other way is also a problem, which isn't really given the same spotlight because it's a problem that doesn't directly affect developers (it merely increases development time, which is an issue of the product owner, not the product developer).
The answer, just like with all things in life, is to find the right balance. Don't blindly err on either side of the scales, because either side will introduce problems. Instead, look at the current situation, evaluate your options (taking past experience into account), and pick the option that has the smallest projected impact on total development time (which includes future maintenance!).
Do not blindly apply blanket rules. Always evaluate the applicability of a rule to your current context before applying it.