You would be breaking the DRY principle putting that validation logic everywhere a zip code is used.
On the other hand, when dealing with many different countries and their different zipcode systems, that means you cannot validate a zipcode unless you know the country in question. So your
ZipCode class needs to also store the country.
But do you then separately store the country as both part of the
Address (which the zipcode is also part of), and part of the zipcode (for validation)?
- If you do, you're violating DRY as well. Even if you don't call it a DRY violation (because each instance serves a different purpose), it's still needlessly taking up extra memory, on top of opening the door to bugs when the two country values are different (which they logically never should be).
- Or, alternatively, it leads to you needing to synchronize the two data points to ensure that they are always the same, which suggests that you should really store this data in a single point anyway, thus defeating the purpose.
- If you don't, then it's not a
ZipCode class but an
Address class, which again will contain a
string ZipCode which means we've come full circle.
For example, I can talk to a business analyst about a Post Code instead of a string that contains a post code.
The benefit is that you can talk about them when describing the domain model.
I don't understand your underlying assertion that when a piece of information has a given variable type, that you're somehow obligated to mention that type whenever you're talking to a business analyst.
Why? Why are you unable to simply talk about "the zipcode" and completely omit the specific type? What kind of discussions are you having with your business (not technical!) analyst where the type of the property is quintessential to the conversation?
Where I'm from, postcodes are always numeric. So we have a choice, we could store it as an
int or as a
string. We tend to use a string because there's no expectation of mathematical operations on the data, but never has a business analyst told me that it needed to be a string. That decision is left up to the developer (or arguably the technical analyst, though in my experience they don't directly deal with the nitty gritty).
A business analyst does not care about the data type, as long as the application does what it's expected to do.
Validation is a tricky beast to tackle, because it relies on what humans expect.
For one, I don't agree with the validation argument as a way to show why primitive obsession should be avoided, because I don't agree that (as a universal truth) data always needs to be validated at all times.
For example, what if this is a more complicated lookup? Rather than a simple format check, what if your validation entails contacting an external API and waiting for a response? Do you really want to force your application to call this external API for every
ZipCode object you instantiate?
Maybe it's a strict business requirement, and then it's of course justifiable. But this is not a universal truth. There will be plenty of use cases where this is more a burden than it is a solution.
As a second example, when entering your address in a form, it's common to enter your postcode before your country. While it's nice to have immediate validation feedback in the UI, it would actually be a hindrance to me (as a user) if the application alerted me to a "wrong" zipcode format, because the real source of the issue is (e.g.) that my country isn't the country that is selected by default, and thus the validation happened for the wrong country.
It's the wrong error message, which distracts the user and causes needless confusion.
Just like how perpetual validation isn't a universal truth, neither are my examples. It's contextual. Some application domains require data validation above all else. Other domains do no put validation that high on the list of priorities because the hassle it brings with it conflicts with their actual priorities (e.g. user experience, or the ability to initially store faulty data so it can be corrected instead of never allowing it to be stored)
Date of Birth: Check that greater than mindate and less than today's date.
Salary: Check that greater than or equal to zero.
The problem with these validations is that they are incomplete, redundant or indicative of a much larger problem.
Checking that a date is greater than the mindate is redundant. The mindate literally means that it is the smallest possible date. Besides, where do you draw the line of relevance? What's the point in preventing
DateTime.MinDate but allowing
DateTime.MinDate.AddSeconds(1)? You're cherrypicking a particular value that is not particularly wrong compared to many other values.
My birthday is Jan 2nd 1978 (it isn't, but let's assume it is). But let's say the data in your application is wrong, and instead it says my birthday is:
- Jan 1st 1978
- Jan 1st 1722
- Jan 1st 2355
All of these dates are wrong. None of them is "more right" than the other. But your validation rule would only catch one of these three examples.
You've also completely omitted the context of how you're using this data. If this is used in e.g. a birthday reminder bot, I'd say the validation is pointless since there is no particular bad consequence to filling in the wrong date.
On the other hand, if this is government data and you need the birthdate to authenticate someone's identity (and failure to do so leads to bad consequences, e.g. denying someone social security), then the correctness of the data is paramount and you need to fully validate the data. The proposed validation you have now is not adequate.
For a salary, there is some common sense in that it cannot be negative. But if you realistically expect that nonsensical data is being entered, I would suggest that you investigate the source of this nonsensical data. Because if they can't be trusted to enter sensical data, you also can't trust them to enter correct data.
If instead the salary is calculated by your application, and somehow it's possible to end up with a negative (and correct) number), then a better approach would be to do
Math.Max(myValue, 0) to turn negative numbers into 0, rather than fail the validation. Because if your logic decided that the outcome is a negative number, failing the validation means it will have to redo the calculation, and there's no reason to think that it will come up with a different number the second time.
And if it does come up with a different number, that again leads you to suspect that the calculation is not consistent and therefore cannot be trusted.
This is not to say that validation isn't useful. But pointless validation is bad, both because it takes effort while not really solving a problem, and giving people a false sense of security.