Is “avoid the yo-yo problem” a reason to allow the “primitive obsession”?
According to When is primitive obsession not a code smell?, I should create a ZipCode object to represent a zip code instead of a String object.
However, in my experience, I prefer to see
public class Address{
public String zipCode;
}
instead of
public class Address{
public ZipCode zipCode;
}
because I think the latter one requires me to move to the ZipCode class to understand the program.
And I believe I need to move between many classes to see the definition if every primitive data fields were replaced by a class, which feels as if suffering from the yo-yo problem (an anti-pattern).
So I would like to move the ZipCode methods into a new class, for example:
Old:
public class ZipCode{
public boolean validate(String zipCode){
}
}
New:
public class ZipCodeHelper{
public static boolean validate(String zipCode){
}
}
so that only the one who needs to validate the zip code would depend on the ZipCodeHelper class. And I found another "benefit" of keeping the primitive obsession: it keeps the class looks like its serialized form, if any, for example: an address table with string column zipCode.
My question is, is "avoiding the yo-yo problem" (move between class definitions) a reason to allow the "primitive obsession"?
object-oriented coding-style
|
show 8 more comments
According to When is primitive obsession not a code smell?, I should create a ZipCode object to represent a zip code instead of a String object.
However, in my experience, I prefer to see
public class Address{
public String zipCode;
}
instead of
public class Address{
public ZipCode zipCode;
}
because I think the latter one requires me to move to the ZipCode class to understand the program.
And I believe I need to move between many classes to see the definition if every primitive data fields were replaced by a class, which feels as if suffering from the yo-yo problem (an anti-pattern).
So I would like to move the ZipCode methods into a new class, for example:
Old:
public class ZipCode{
public boolean validate(String zipCode){
}
}
New:
public class ZipCodeHelper{
public static boolean validate(String zipCode){
}
}
so that only the one who needs to validate the zip code would depend on the ZipCodeHelper class. And I found another "benefit" of keeping the primitive obsession: it keeps the class looks like its serialized form, if any, for example: an address table with string column zipCode.
My question is, is "avoiding the yo-yo problem" (move between class definitions) a reason to allow the "primitive obsession"?
object-oriented coding-style
2
A nice read concerning adresses: mjt.me.uk/posts/falsehoods-programmers-believe-about-addresses Make sure you understand what you need and if a zipcode that is part of an address is really what you need and want to model.
– Polygnome
yesterday
1
"I should create a ZipCode object to represent zip code instead of a String object." ...Why? Zip codes would have no additional functionality to attach, and the most common thing you'll do with them is render them as part of a full address. Maybe you'll pass them to a geocoding service, but a custom object makes that harder. The Address class itself can validate (which shouldn't be more than just correct length and optional parts to avoid restricting valid inputs unknown to you at delivery time). I just don't see any practical advantage to having a zip code object; it's just boilerplate.
– jpmc26
19 hours ago
4
@jpmc26 Then you would be shocked to see how complex our zip code object is -- not saying it's right, but it does exist
– Jared Goguen
15 hours ago
3
@jpmc26, I fail to see how you get from "complex" to "badly-designed." Complex code is often the result of simple code coming into contact with the complexity of the real world rather than the ideal world we might wish existed. "Back to that two page function. Yes, I know, it’s just a simple function to display a window, but it has grown little hairs and stuff on it and nobody knows why. Well, I’ll tell you why: those are bug fixes."
– Kyralessa
9 hours ago
4
@jpmc26 - the point of wrapping objects like ZipCode is type safety. Zip code is not a string, it's a zip code. If a function expects a zip code, you should only be able to pass a zip code, not a string.
– Davor Ždralo
6 hours ago
|
show 8 more comments
According to When is primitive obsession not a code smell?, I should create a ZipCode object to represent a zip code instead of a String object.
However, in my experience, I prefer to see
public class Address{
public String zipCode;
}
instead of
public class Address{
public ZipCode zipCode;
}
because I think the latter one requires me to move to the ZipCode class to understand the program.
And I believe I need to move between many classes to see the definition if every primitive data fields were replaced by a class, which feels as if suffering from the yo-yo problem (an anti-pattern).
So I would like to move the ZipCode methods into a new class, for example:
Old:
public class ZipCode{
public boolean validate(String zipCode){
}
}
New:
public class ZipCodeHelper{
public static boolean validate(String zipCode){
}
}
so that only the one who needs to validate the zip code would depend on the ZipCodeHelper class. And I found another "benefit" of keeping the primitive obsession: it keeps the class looks like its serialized form, if any, for example: an address table with string column zipCode.
My question is, is "avoiding the yo-yo problem" (move between class definitions) a reason to allow the "primitive obsession"?
object-oriented coding-style
According to When is primitive obsession not a code smell?, I should create a ZipCode object to represent a zip code instead of a String object.
However, in my experience, I prefer to see
public class Address{
public String zipCode;
}
instead of
public class Address{
public ZipCode zipCode;
}
because I think the latter one requires me to move to the ZipCode class to understand the program.
And I believe I need to move between many classes to see the definition if every primitive data fields were replaced by a class, which feels as if suffering from the yo-yo problem (an anti-pattern).
So I would like to move the ZipCode methods into a new class, for example:
Old:
public class ZipCode{
public boolean validate(String zipCode){
}
}
New:
public class ZipCodeHelper{
public static boolean validate(String zipCode){
}
}
so that only the one who needs to validate the zip code would depend on the ZipCodeHelper class. And I found another "benefit" of keeping the primitive obsession: it keeps the class looks like its serialized form, if any, for example: an address table with string column zipCode.
My question is, is "avoiding the yo-yo problem" (move between class definitions) a reason to allow the "primitive obsession"?
object-oriented coding-style
object-oriented coding-style
edited 5 hours ago
Peter Mortensen
1,11521114
1,11521114
asked yesterday
mmmaaammmaaa
2,30931421
2,30931421
2
A nice read concerning adresses: mjt.me.uk/posts/falsehoods-programmers-believe-about-addresses Make sure you understand what you need and if a zipcode that is part of an address is really what you need and want to model.
– Polygnome
yesterday
1
"I should create a ZipCode object to represent zip code instead of a String object." ...Why? Zip codes would have no additional functionality to attach, and the most common thing you'll do with them is render them as part of a full address. Maybe you'll pass them to a geocoding service, but a custom object makes that harder. The Address class itself can validate (which shouldn't be more than just correct length and optional parts to avoid restricting valid inputs unknown to you at delivery time). I just don't see any practical advantage to having a zip code object; it's just boilerplate.
– jpmc26
19 hours ago
4
@jpmc26 Then you would be shocked to see how complex our zip code object is -- not saying it's right, but it does exist
– Jared Goguen
15 hours ago
3
@jpmc26, I fail to see how you get from "complex" to "badly-designed." Complex code is often the result of simple code coming into contact with the complexity of the real world rather than the ideal world we might wish existed. "Back to that two page function. Yes, I know, it’s just a simple function to display a window, but it has grown little hairs and stuff on it and nobody knows why. Well, I’ll tell you why: those are bug fixes."
– Kyralessa
9 hours ago
4
@jpmc26 - the point of wrapping objects like ZipCode is type safety. Zip code is not a string, it's a zip code. If a function expects a zip code, you should only be able to pass a zip code, not a string.
– Davor Ždralo
6 hours ago
|
show 8 more comments
2
A nice read concerning adresses: mjt.me.uk/posts/falsehoods-programmers-believe-about-addresses Make sure you understand what you need and if a zipcode that is part of an address is really what you need and want to model.
– Polygnome
yesterday
1
"I should create a ZipCode object to represent zip code instead of a String object." ...Why? Zip codes would have no additional functionality to attach, and the most common thing you'll do with them is render them as part of a full address. Maybe you'll pass them to a geocoding service, but a custom object makes that harder. The Address class itself can validate (which shouldn't be more than just correct length and optional parts to avoid restricting valid inputs unknown to you at delivery time). I just don't see any practical advantage to having a zip code object; it's just boilerplate.
– jpmc26
19 hours ago
4
@jpmc26 Then you would be shocked to see how complex our zip code object is -- not saying it's right, but it does exist
– Jared Goguen
15 hours ago
3
@jpmc26, I fail to see how you get from "complex" to "badly-designed." Complex code is often the result of simple code coming into contact with the complexity of the real world rather than the ideal world we might wish existed. "Back to that two page function. Yes, I know, it’s just a simple function to display a window, but it has grown little hairs and stuff on it and nobody knows why. Well, I’ll tell you why: those are bug fixes."
– Kyralessa
9 hours ago
4
@jpmc26 - the point of wrapping objects like ZipCode is type safety. Zip code is not a string, it's a zip code. If a function expects a zip code, you should only be able to pass a zip code, not a string.
– Davor Ždralo
6 hours ago
2
2
A nice read concerning adresses: mjt.me.uk/posts/falsehoods-programmers-believe-about-addresses Make sure you understand what you need and if a zipcode that is part of an address is really what you need and want to model.
– Polygnome
yesterday
A nice read concerning adresses: mjt.me.uk/posts/falsehoods-programmers-believe-about-addresses Make sure you understand what you need and if a zipcode that is part of an address is really what you need and want to model.
– Polygnome
yesterday
1
1
"I should create a ZipCode object to represent zip code instead of a String object." ...Why? Zip codes would have no additional functionality to attach, and the most common thing you'll do with them is render them as part of a full address. Maybe you'll pass them to a geocoding service, but a custom object makes that harder. The Address class itself can validate (which shouldn't be more than just correct length and optional parts to avoid restricting valid inputs unknown to you at delivery time). I just don't see any practical advantage to having a zip code object; it's just boilerplate.
– jpmc26
19 hours ago
"I should create a ZipCode object to represent zip code instead of a String object." ...Why? Zip codes would have no additional functionality to attach, and the most common thing you'll do with them is render them as part of a full address. Maybe you'll pass them to a geocoding service, but a custom object makes that harder. The Address class itself can validate (which shouldn't be more than just correct length and optional parts to avoid restricting valid inputs unknown to you at delivery time). I just don't see any practical advantage to having a zip code object; it's just boilerplate.
– jpmc26
19 hours ago
4
4
@jpmc26 Then you would be shocked to see how complex our zip code object is -- not saying it's right, but it does exist
– Jared Goguen
15 hours ago
@jpmc26 Then you would be shocked to see how complex our zip code object is -- not saying it's right, but it does exist
– Jared Goguen
15 hours ago
3
3
@jpmc26, I fail to see how you get from "complex" to "badly-designed." Complex code is often the result of simple code coming into contact with the complexity of the real world rather than the ideal world we might wish existed. "Back to that two page function. Yes, I know, it’s just a simple function to display a window, but it has grown little hairs and stuff on it and nobody knows why. Well, I’ll tell you why: those are bug fixes."
– Kyralessa
9 hours ago
@jpmc26, I fail to see how you get from "complex" to "badly-designed." Complex code is often the result of simple code coming into contact with the complexity of the real world rather than the ideal world we might wish existed. "Back to that two page function. Yes, I know, it’s just a simple function to display a window, but it has grown little hairs and stuff on it and nobody knows why. Well, I’ll tell you why: those are bug fixes."
– Kyralessa
9 hours ago
4
4
@jpmc26 - the point of wrapping objects like ZipCode is type safety. Zip code is not a string, it's a zip code. If a function expects a zip code, you should only be able to pass a zip code, not a string.
– Davor Ždralo
6 hours ago
@jpmc26 - the point of wrapping objects like ZipCode is type safety. Zip code is not a string, it's a zip code. If a function expects a zip code, you should only be able to pass a zip code, not a string.
– Davor Ždralo
6 hours ago
|
show 8 more comments
6 Answers
6
active
oldest
votes
The assumption is that you don't need to yo-yo to the ZipCode class to understand the Address class. If ZipCode is well-designed it should be obvious what it does just by reading the Address class.
Programs are not read end-to-end - typically programs are far too complex to make this possible. You cannot keep all the code in a program in your mind at the same time. So we use abstractions and encapsulations to "chunk" the program into meaningful units, so you can look at one part of the program (say the Address class) without having to read all code it depends on.
For example I'm sure you don't yo-yo into reading the source code for String every time you encounter String in code.
Renaming the class from ZipCode to ZipCodeHelper suggest there now is two separate concepts: a zip code and a zip code helper. So twice as complex. And now the type system cannot help you distinguish between an arbitrary string and a valid zip code since they have the same type. This is where "obsession" is appropriate: You are suggesting a more complex and less safe alternative just because you want to avoid a simple wrapper type around a primitive.
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. But as soon as you add any logic, it is much simpler if this logic is encapsulated with the type.
As for serialization I think it sounds like a limitation in the framework you are using. Surely you should be able to serialize a ZipCode to a string or map it to a column in a database.
1
I agree with the "meaningful units" (main-) part, but not so much that a zip code and zip code validation are the same concept.ZipCodeHelper
(which I would rather callZipCodeValidator
) might very well establish a connection to a web service to do it's job. That would not be part of the single responsibility "hold the zip code data". Making the type system disallow invalid zip codes can still be achieved by making theZipCode
constructor the equivalent of Java's package-private and calling that with aZipCodeFactory
which always calls the validator.
– R. Schmitz
yesterday
10
@R.Schmitz: That is not what "responsibility" means in the sense of the single responsibility principle. But in any case, you should of course use as many classes as you need as long as you encapsulate the zip code and its validation. The OP suggest a helper instead of encapsulating the zip code, which is a bad idea.
– JacquesB
yesterday
I want to respectfully disagree. SRP means a class should have "one, and only one, reason to be changed" (change in "what a zipcode consists of" vs. "how it is validated"). This specific case here is further elaborated on in the book Clean Code: "Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions." -ZipCode
would be a "data structure" andZipCodeHelper
an "object' . In any case, I think we agree that we shouldn't have to pass web connections to the ZipCode constructor.
– R. Schmitz
yesterday
5
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. => I disagree. Even if all values are valid, I would still favor conveying the semantics to the language rather than use primitives. If a function can be called on a primitive type which is nonsensical for its current semantic usage, then it should not be a primitive type, it should be a proper type with only the sensible functions defined. (As an example, usingint
as ID allows multiplying an ID by an ID...)
– Matthieu M.
23 hours ago
@R.Schmitz I think ZIP codes are a poor example for the distinction you're making. Something that changes often might be a candidate for separateFoo
andFooValidator
classes. We could have aZipCode
class that validates the format and aZipCodeValidator
that hits some Web service to check that a correctly formattedZipCode
is actually current. We know that ZIP codes change. But practically, we're going to have a list of valid ZIP codes encapsulated inZipCode
, or in some local database.
– TKK
23 hours ago
|
show 9 more comments
If can do:
new ZipCode("totally invalid zip code");
And the constructor for ZipCode does:
ZipCodeHelper.validate("totally invalid zip code");
Then you've broken encapsulation, and added a pretty silly dependency to the ZipCode class. If the constructor doesn't call ZipCodeHelper.validate(...)
then you have isolated logic in its own island without actually enforcing it. You can create invalid zip codes.
The validate
method should be a static method on the ZipCode class. Now the knowledge of a "valid" zip code is bundled together with the ZipCode class. Given that your code examples look like Java, the constructor of ZipCode should throw an exception if an incorrect format is given:
public class ZipCode {
private String zipCode;
public ZipCode(string zipCode) {
if (!validate(zipCode))
throw new IllegalFormatException("Invalid zip code");
this.zipCode = zipCode;
}
public static bool validate(String zipCode) {
// logic to check format
}
@Override
public String toString() {
return zipCode;
}
}
The constructor checks the format and throws an exception, thereby preventing invalid zip codes from being created, and the static validate
method is available to other code so the logic of checking the format is encapsulated in the ZipCode class.
There is no "yo-yo" in this variant of the ZipCode class. It's just called proper Object Oriented Programming.
We are also going to ignore internationalization here, which may necessitate another class called ZipCodeFormat or PostalService (e.g. PostalService.isValidPostalCode(...), PostalService.parsePostalCode(...), etc.).
13
Note: The main advantage with @Greg Burkhardt's approach here is that if someone gives you a ZipCode object, you can trust that it contains a valid string without having to check it again, since its type and the fact that it was successfully constructed gives you that guarantee. If you instead passed strings around, you might feel a need to "assert validate(zipCode)" at various places in your code just to be sure that you had a valid zip code, but with a successfully constructed ZipCode object, you can trust that its contents are valid without having to check them again.
– Some Guy
23 hours ago
@SomeGuy While the disadvantage is that you're having a "vexing" exception here. Either you surround every ZipCode creation with a try-catch, or you always check it twice. However, as long as your validation code is minor, this should pose no issue for internal, embedded, game and throwaway apps.
– R. Schmitz
9 hours ago
1
@R.Schmitz: TheZipCode.validate
method is the pre-check that can be performed before invoking a constructor that throws an exception.
– Greg Burghardt
7 hours ago
3
@R.Schmitz: If you are concerned about a vexing exception, an alternate approach to construction is to make the ZipCode constructor private, and provide a public static factory function (Zipcode.create?) that performs validation of the passed-in parameters, returns null if unsuccessful, and otherwise constructs a ZipCode object and returns it. The caller will always have to check for a null return value, of course. On the other hand, if you are in the habit, for instance, of always validating (regex? validate? etc.) before constructing a ZipCode, the exception may not be so vexing in practice.
– Some Guy
6 hours ago
3
A factory function that returns an Optional<ZipCode> is also a possibility. Then the caller has no choice but to explicitly handle possible failure of the factory function. Regardless, in either case, the error will be discovered somewhere near where it was created rather than possibly much later, by client code far from the original problem.
– Some Guy
6 hours ago
|
show 2 more comments
If you wrestle a lot with this question, perhaps the language you use is not the right tool for the job?
This kind of "domain-typed primitives" are trivially easy to express in, for example, F#.
There you could, for example, write:
type ZipCode = string
type Town = string
type Adress = {
zipCode: ZipCode
town: Town
//etc
}
let adress1 = {
zipCode = ZipCode "90210"
town = Town "Beverly Hills"
}
let faultyAdress = {
zipCode = "12345" // <-Compiler error
town = adress1.zipCode // <- Compiler error
}
This is really useful for avoiding common mistakes, like comparing id's of different entities. And since these typed primitives are much more lightweight than a C# or Java-class, you'll end up actually use them.
Interesting - how would it look like if you wanted to enforce validation ofZipCode
?
– Hulk
11 hours ago
2
@Hulk You can write OO-style in F# and make the types into classes. However, I prefer functional style, declaring the type with type ZipCode = private ZipCode of string and adding a ZipCode module with a create function. There are some examples here: gist.github.com/swlaschin/54cfff886669ccab895a
– Guran
11 hours ago
add a comment |
The ZipCode
abstraction could only make sense if your Address
class did not also have a TownName
property. Otherwise, you have half an abstraction: the zip code designates the town, but these two related bits of information are found in different classes. It doesn't quite make sense.
However, even then, it's still not a correct application (or rather solution to) primitive obsession; which, as I understand it, mainly focuses on two things:
- Using primitives as the input (or even output) values of a method, especially when a collection of primitives is needed.
- Classes that grow extra properties over time without ever reconsidering whether some of these should be grouped into a subclass of their own.
Your case is neither. An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country, ...). There is little to no reason to break up this data as it has a single responsibility: designate a location on Earth. An address requires all of these fields in order to be meaningful. Half an address is pointless.
This is how you know that you don't need to subdivide any further: breaking it down any further would detract from the functional intention of the Address
class. Similarly, you don't need a Name
subclass to be used in the Person
class, unless Name
(without a person attached) is a meaningful concept in your domain. Which it (usually) isn't. Names are used for identifying people, they usually have no value on their own.
1
@RikD: From the answer: "you don't need aName
subclass to be used in thePerson
class, unless Name (without a person attached) is a meaningful concept in your domain." When you have custom validation for the names, the name has then become a meaningful concept in your domain; which I explicitly mentioned as a valid use case for using a subtype. Secondly, for the zipcode validation, you're introducing additional assumptions, such as zip codes needing to follow a given country's format. You're broaching a topic that's much broader than the intent of OP's question.
– Flater
yesterday
4
"An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country)." - Well that's just plain wrong. For a good way to deal with this, look at Amazon's address form.
– R. Schmitz
yesterday
1
@R.Schmitz The format of an address is different from the properties it has. Yes, culturally it can be formatted different ways, but the property declaration is not responsible for making that decision. It only defines which fields are possible. My answere did not address address validation, which your linked page is mostly about (that and formats, also not my focus). Additionally, not every culture has these wildly varying address formats (Belgium and the Netherlands have a rather fixed format for all listed cases). Additionally, not every application is built to be used internationally.
– Flater
yesterday
3
@Flater Well I won't blame you for not reading the full list of falsehoods, because it's quite long, but it literally contains "Addresses will have a street", "An address require both a city and a country", "An address will have a postcode" etc., which is contrary to what the quoted sentence says.
– R. Schmitz
yesterday
7
@GregBurghardt "Zip code assumes United States Postal Service, and you can derive the name of the town from the zip code. Cities can have multiple zip codes, but each zip code is only tied to 1 city." This is not correct in general. I have a zipcode that is used mainly for a neighboring city but my residence is not located there. Zipcodes do not always align with governmental boundaries. For example 42223 contains counties from both TN and KY.
– JimmyJames
yesterday
|
show 12 more comments
The answer depends entirely on what you actually want to do with the ZIP codes. Here are two extreme possibilities:
(1) All addresses are guaranteed to be in a single country. No exceptions at all. (E.g. no foreign customers, or no employees whose private address is abroad while they are working for a foreign customer.) This country has ZIP codes and they can be expected to never be seriously problematic (i.e. they don't require free-form input such as "currently D4B 6N2, but this changes every 2 weeks"). The ZIP codes are used not just for addressing, but for validation of payment information or similar purposes. - Under these circumstances, a ZIP code class makes a lot of sense.
(2) Addresses can be in almost every country, so dozens or hundreds of addressing schemes with or without ZIP codes (and with thousands of weird exceptions and special cases) are relevant. A "ZIP" code is really only asked for to remind people from countries where ZIP codes are used not to forget to provide theirs. The addresses are only used so that if someone loses access to their account and they can prove their name and address, access will be restored. - Under these circumstances, ZIP code classes for all relevant countries would be an enormous effort. Fortunately they are not needed at all.
add a comment |
The other answers have talked about OO domain modelling and using a richer type to represent your value.
I don't disagree, especially given the example code you posted.
But I also wonder if that actually answers the title of your question.
Consider the following scenario (pulled from an actual project I'm working on):
You have a remote application on a field device that talks to your central server. One of the DB fields for the device entry is a zip code for the address that the field device is at. You don't care about the zip code (or any of the rest of the address for that matter). All of the people who care about it are on the other side of an HTTP boundary: you just happen to be the single source of truth for the data. It has no place in your domain modeling. You just record it, validate it, store it, and on request shuffle it off in a JSON blob to points elsewhere.
In this scenario, doing much of anything beyond validating the insert with an SQL regex constraint (or its ORM equivalent) is probably overkill of the YAGNI variety.
5
Your SQL regex constraint could be viewed as a qualified type - within your database, the Zip code is not stored as "VarChar" but "VarChar constrained by this rule". In some DBMSes, you could easily give that type+constraint a name as a reusable "domain type", and we are back in the recommended place of giving the data a meaningful type. I agree with your answer in principle, but don't think that example matches; a better example would be if your data is "raw sensor data", and the most meaningful type is "byte array" because you have no idea what the data means.
– IMSoP
yesterday
@IMSoP interesting point. Not sure I agree though: you could validate a string zip code in Java (or any other language) with a regex but still be dealing with it as a string instead of a richer type. Depending on the domain logic, further manipulation might be required (for instance ensuring that the zip code matches the state, something that would be difficult/impossible to validate with regex).
– Jared Smith
yesterday
You could, but as soon as you do so, you are ascribing it some domain-specific behaviour, and that is exactly what the quoted articles are saying should lead to the creation of a custom type. The question is not whether you can do it in one style or the other, but whether you should, assuming your programming language gives you the choice.
– IMSoP
yesterday
You could model such a thing as aRegexValidatedString
, containing the string itself and the regex used to validate it. But unless every instance has a unique regex (which is possible but unlikely) this seems a bit silly and wasteful of memory (and possibly regex compilation time). So you either put the regex into a separate table and leave behind a lookup key in each instance to find it (which is arguably worse due to indirection) or you find some way to store it once for each common type of value sharing that rule -- eg. a static field on a domain type, or equivalent method, as IMSoP said.
– Miral
16 hours ago
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "131"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: false,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fsoftwareengineering.stackexchange.com%2fquestions%2f386042%2fis-avoid-the-yo-yo-problem-a-reason-to-allow-the-primitive-obsession%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
StackExchange.ready(function () {
$("#show-editor-button input, #show-editor-button button").click(function () {
var showEditor = function() {
$("#show-editor-button").hide();
$("#post-form").removeClass("dno");
StackExchange.editor.finallyInit();
};
var useFancy = $(this).data('confirm-use-fancy');
if(useFancy == 'True') {
var popupTitle = $(this).data('confirm-fancy-title');
var popupBody = $(this).data('confirm-fancy-body');
var popupAccept = $(this).data('confirm-fancy-accept-button');
$(this).loadPopup({
url: '/post/self-answer-popup',
loaded: function(popup) {
var pTitle = $(popup).find('h2');
var pBody = $(popup).find('.popup-body');
var pSubmit = $(popup).find('.popup-submit');
pTitle.text(popupTitle);
pBody.html(popupBody);
pSubmit.val(popupAccept).click(showEditor);
}
})
} else{
var confirmText = $(this).data('confirm-text');
if (confirmText ? confirm(confirmText) : true) {
showEditor();
}
}
});
});
6 Answers
6
active
oldest
votes
6 Answers
6
active
oldest
votes
active
oldest
votes
active
oldest
votes
The assumption is that you don't need to yo-yo to the ZipCode class to understand the Address class. If ZipCode is well-designed it should be obvious what it does just by reading the Address class.
Programs are not read end-to-end - typically programs are far too complex to make this possible. You cannot keep all the code in a program in your mind at the same time. So we use abstractions and encapsulations to "chunk" the program into meaningful units, so you can look at one part of the program (say the Address class) without having to read all code it depends on.
For example I'm sure you don't yo-yo into reading the source code for String every time you encounter String in code.
Renaming the class from ZipCode to ZipCodeHelper suggest there now is two separate concepts: a zip code and a zip code helper. So twice as complex. And now the type system cannot help you distinguish between an arbitrary string and a valid zip code since they have the same type. This is where "obsession" is appropriate: You are suggesting a more complex and less safe alternative just because you want to avoid a simple wrapper type around a primitive.
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. But as soon as you add any logic, it is much simpler if this logic is encapsulated with the type.
As for serialization I think it sounds like a limitation in the framework you are using. Surely you should be able to serialize a ZipCode to a string or map it to a column in a database.
1
I agree with the "meaningful units" (main-) part, but not so much that a zip code and zip code validation are the same concept.ZipCodeHelper
(which I would rather callZipCodeValidator
) might very well establish a connection to a web service to do it's job. That would not be part of the single responsibility "hold the zip code data". Making the type system disallow invalid zip codes can still be achieved by making theZipCode
constructor the equivalent of Java's package-private and calling that with aZipCodeFactory
which always calls the validator.
– R. Schmitz
yesterday
10
@R.Schmitz: That is not what "responsibility" means in the sense of the single responsibility principle. But in any case, you should of course use as many classes as you need as long as you encapsulate the zip code and its validation. The OP suggest a helper instead of encapsulating the zip code, which is a bad idea.
– JacquesB
yesterday
I want to respectfully disagree. SRP means a class should have "one, and only one, reason to be changed" (change in "what a zipcode consists of" vs. "how it is validated"). This specific case here is further elaborated on in the book Clean Code: "Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions." -ZipCode
would be a "data structure" andZipCodeHelper
an "object' . In any case, I think we agree that we shouldn't have to pass web connections to the ZipCode constructor.
– R. Schmitz
yesterday
5
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. => I disagree. Even if all values are valid, I would still favor conveying the semantics to the language rather than use primitives. If a function can be called on a primitive type which is nonsensical for its current semantic usage, then it should not be a primitive type, it should be a proper type with only the sensible functions defined. (As an example, usingint
as ID allows multiplying an ID by an ID...)
– Matthieu M.
23 hours ago
@R.Schmitz I think ZIP codes are a poor example for the distinction you're making. Something that changes often might be a candidate for separateFoo
andFooValidator
classes. We could have aZipCode
class that validates the format and aZipCodeValidator
that hits some Web service to check that a correctly formattedZipCode
is actually current. We know that ZIP codes change. But practically, we're going to have a list of valid ZIP codes encapsulated inZipCode
, or in some local database.
– TKK
23 hours ago
|
show 9 more comments
The assumption is that you don't need to yo-yo to the ZipCode class to understand the Address class. If ZipCode is well-designed it should be obvious what it does just by reading the Address class.
Programs are not read end-to-end - typically programs are far too complex to make this possible. You cannot keep all the code in a program in your mind at the same time. So we use abstractions and encapsulations to "chunk" the program into meaningful units, so you can look at one part of the program (say the Address class) without having to read all code it depends on.
For example I'm sure you don't yo-yo into reading the source code for String every time you encounter String in code.
Renaming the class from ZipCode to ZipCodeHelper suggest there now is two separate concepts: a zip code and a zip code helper. So twice as complex. And now the type system cannot help you distinguish between an arbitrary string and a valid zip code since they have the same type. This is where "obsession" is appropriate: You are suggesting a more complex and less safe alternative just because you want to avoid a simple wrapper type around a primitive.
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. But as soon as you add any logic, it is much simpler if this logic is encapsulated with the type.
As for serialization I think it sounds like a limitation in the framework you are using. Surely you should be able to serialize a ZipCode to a string or map it to a column in a database.
1
I agree with the "meaningful units" (main-) part, but not so much that a zip code and zip code validation are the same concept.ZipCodeHelper
(which I would rather callZipCodeValidator
) might very well establish a connection to a web service to do it's job. That would not be part of the single responsibility "hold the zip code data". Making the type system disallow invalid zip codes can still be achieved by making theZipCode
constructor the equivalent of Java's package-private and calling that with aZipCodeFactory
which always calls the validator.
– R. Schmitz
yesterday
10
@R.Schmitz: That is not what "responsibility" means in the sense of the single responsibility principle. But in any case, you should of course use as many classes as you need as long as you encapsulate the zip code and its validation. The OP suggest a helper instead of encapsulating the zip code, which is a bad idea.
– JacquesB
yesterday
I want to respectfully disagree. SRP means a class should have "one, and only one, reason to be changed" (change in "what a zipcode consists of" vs. "how it is validated"). This specific case here is further elaborated on in the book Clean Code: "Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions." -ZipCode
would be a "data structure" andZipCodeHelper
an "object' . In any case, I think we agree that we shouldn't have to pass web connections to the ZipCode constructor.
– R. Schmitz
yesterday
5
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. => I disagree. Even if all values are valid, I would still favor conveying the semantics to the language rather than use primitives. If a function can be called on a primitive type which is nonsensical for its current semantic usage, then it should not be a primitive type, it should be a proper type with only the sensible functions defined. (As an example, usingint
as ID allows multiplying an ID by an ID...)
– Matthieu M.
23 hours ago
@R.Schmitz I think ZIP codes are a poor example for the distinction you're making. Something that changes often might be a candidate for separateFoo
andFooValidator
classes. We could have aZipCode
class that validates the format and aZipCodeValidator
that hits some Web service to check that a correctly formattedZipCode
is actually current. We know that ZIP codes change. But practically, we're going to have a list of valid ZIP codes encapsulated inZipCode
, or in some local database.
– TKK
23 hours ago
|
show 9 more comments
The assumption is that you don't need to yo-yo to the ZipCode class to understand the Address class. If ZipCode is well-designed it should be obvious what it does just by reading the Address class.
Programs are not read end-to-end - typically programs are far too complex to make this possible. You cannot keep all the code in a program in your mind at the same time. So we use abstractions and encapsulations to "chunk" the program into meaningful units, so you can look at one part of the program (say the Address class) without having to read all code it depends on.
For example I'm sure you don't yo-yo into reading the source code for String every time you encounter String in code.
Renaming the class from ZipCode to ZipCodeHelper suggest there now is two separate concepts: a zip code and a zip code helper. So twice as complex. And now the type system cannot help you distinguish between an arbitrary string and a valid zip code since they have the same type. This is where "obsession" is appropriate: You are suggesting a more complex and less safe alternative just because you want to avoid a simple wrapper type around a primitive.
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. But as soon as you add any logic, it is much simpler if this logic is encapsulated with the type.
As for serialization I think it sounds like a limitation in the framework you are using. Surely you should be able to serialize a ZipCode to a string or map it to a column in a database.
The assumption is that you don't need to yo-yo to the ZipCode class to understand the Address class. If ZipCode is well-designed it should be obvious what it does just by reading the Address class.
Programs are not read end-to-end - typically programs are far too complex to make this possible. You cannot keep all the code in a program in your mind at the same time. So we use abstractions and encapsulations to "chunk" the program into meaningful units, so you can look at one part of the program (say the Address class) without having to read all code it depends on.
For example I'm sure you don't yo-yo into reading the source code for String every time you encounter String in code.
Renaming the class from ZipCode to ZipCodeHelper suggest there now is two separate concepts: a zip code and a zip code helper. So twice as complex. And now the type system cannot help you distinguish between an arbitrary string and a valid zip code since they have the same type. This is where "obsession" is appropriate: You are suggesting a more complex and less safe alternative just because you want to avoid a simple wrapper type around a primitive.
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. But as soon as you add any logic, it is much simpler if this logic is encapsulated with the type.
As for serialization I think it sounds like a limitation in the framework you are using. Surely you should be able to serialize a ZipCode to a string or map it to a column in a database.
edited yesterday
Rik D
16115
16115
answered yesterday
JacquesBJacquesB
42.3k1688124
42.3k1688124
1
I agree with the "meaningful units" (main-) part, but not so much that a zip code and zip code validation are the same concept.ZipCodeHelper
(which I would rather callZipCodeValidator
) might very well establish a connection to a web service to do it's job. That would not be part of the single responsibility "hold the zip code data". Making the type system disallow invalid zip codes can still be achieved by making theZipCode
constructor the equivalent of Java's package-private and calling that with aZipCodeFactory
which always calls the validator.
– R. Schmitz
yesterday
10
@R.Schmitz: That is not what "responsibility" means in the sense of the single responsibility principle. But in any case, you should of course use as many classes as you need as long as you encapsulate the zip code and its validation. The OP suggest a helper instead of encapsulating the zip code, which is a bad idea.
– JacquesB
yesterday
I want to respectfully disagree. SRP means a class should have "one, and only one, reason to be changed" (change in "what a zipcode consists of" vs. "how it is validated"). This specific case here is further elaborated on in the book Clean Code: "Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions." -ZipCode
would be a "data structure" andZipCodeHelper
an "object' . In any case, I think we agree that we shouldn't have to pass web connections to the ZipCode constructor.
– R. Schmitz
yesterday
5
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. => I disagree. Even if all values are valid, I would still favor conveying the semantics to the language rather than use primitives. If a function can be called on a primitive type which is nonsensical for its current semantic usage, then it should not be a primitive type, it should be a proper type with only the sensible functions defined. (As an example, usingint
as ID allows multiplying an ID by an ID...)
– Matthieu M.
23 hours ago
@R.Schmitz I think ZIP codes are a poor example for the distinction you're making. Something that changes often might be a candidate for separateFoo
andFooValidator
classes. We could have aZipCode
class that validates the format and aZipCodeValidator
that hits some Web service to check that a correctly formattedZipCode
is actually current. We know that ZIP codes change. But practically, we're going to have a list of valid ZIP codes encapsulated inZipCode
, or in some local database.
– TKK
23 hours ago
|
show 9 more comments
1
I agree with the "meaningful units" (main-) part, but not so much that a zip code and zip code validation are the same concept.ZipCodeHelper
(which I would rather callZipCodeValidator
) might very well establish a connection to a web service to do it's job. That would not be part of the single responsibility "hold the zip code data". Making the type system disallow invalid zip codes can still be achieved by making theZipCode
constructor the equivalent of Java's package-private and calling that with aZipCodeFactory
which always calls the validator.
– R. Schmitz
yesterday
10
@R.Schmitz: That is not what "responsibility" means in the sense of the single responsibility principle. But in any case, you should of course use as many classes as you need as long as you encapsulate the zip code and its validation. The OP suggest a helper instead of encapsulating the zip code, which is a bad idea.
– JacquesB
yesterday
I want to respectfully disagree. SRP means a class should have "one, and only one, reason to be changed" (change in "what a zipcode consists of" vs. "how it is validated"). This specific case here is further elaborated on in the book Clean Code: "Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions." -ZipCode
would be a "data structure" andZipCodeHelper
an "object' . In any case, I think we agree that we shouldn't have to pass web connections to the ZipCode constructor.
– R. Schmitz
yesterday
5
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. => I disagree. Even if all values are valid, I would still favor conveying the semantics to the language rather than use primitives. If a function can be called on a primitive type which is nonsensical for its current semantic usage, then it should not be a primitive type, it should be a proper type with only the sensible functions defined. (As an example, usingint
as ID allows multiplying an ID by an ID...)
– Matthieu M.
23 hours ago
@R.Schmitz I think ZIP codes are a poor example for the distinction you're making. Something that changes often might be a candidate for separateFoo
andFooValidator
classes. We could have aZipCode
class that validates the format and aZipCodeValidator
that hits some Web service to check that a correctly formattedZipCode
is actually current. We know that ZIP codes change. But practically, we're going to have a list of valid ZIP codes encapsulated inZipCode
, or in some local database.
– TKK
23 hours ago
1
1
I agree with the "meaningful units" (main-) part, but not so much that a zip code and zip code validation are the same concept.
ZipCodeHelper
(which I would rather call ZipCodeValidator
) might very well establish a connection to a web service to do it's job. That would not be part of the single responsibility "hold the zip code data". Making the type system disallow invalid zip codes can still be achieved by making the ZipCode
constructor the equivalent of Java's package-private and calling that with a ZipCodeFactory
which always calls the validator.– R. Schmitz
yesterday
I agree with the "meaningful units" (main-) part, but not so much that a zip code and zip code validation are the same concept.
ZipCodeHelper
(which I would rather call ZipCodeValidator
) might very well establish a connection to a web service to do it's job. That would not be part of the single responsibility "hold the zip code data". Making the type system disallow invalid zip codes can still be achieved by making the ZipCode
constructor the equivalent of Java's package-private and calling that with a ZipCodeFactory
which always calls the validator.– R. Schmitz
yesterday
10
10
@R.Schmitz: That is not what "responsibility" means in the sense of the single responsibility principle. But in any case, you should of course use as many classes as you need as long as you encapsulate the zip code and its validation. The OP suggest a helper instead of encapsulating the zip code, which is a bad idea.
– JacquesB
yesterday
@R.Schmitz: That is not what "responsibility" means in the sense of the single responsibility principle. But in any case, you should of course use as many classes as you need as long as you encapsulate the zip code and its validation. The OP suggest a helper instead of encapsulating the zip code, which is a bad idea.
– JacquesB
yesterday
I want to respectfully disagree. SRP means a class should have "one, and only one, reason to be changed" (change in "what a zipcode consists of" vs. "how it is validated"). This specific case here is further elaborated on in the book Clean Code: "Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions." -
ZipCode
would be a "data structure" and ZipCodeHelper
an "object' . In any case, I think we agree that we shouldn't have to pass web connections to the ZipCode constructor.– R. Schmitz
yesterday
I want to respectfully disagree. SRP means a class should have "one, and only one, reason to be changed" (change in "what a zipcode consists of" vs. "how it is validated"). This specific case here is further elaborated on in the book Clean Code: "Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions." -
ZipCode
would be a "data structure" and ZipCodeHelper
an "object' . In any case, I think we agree that we shouldn't have to pass web connections to the ZipCode constructor.– R. Schmitz
yesterday
5
5
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. => I disagree. Even if all values are valid, I would still favor conveying the semantics to the language rather than use primitives. If a function can be called on a primitive type which is nonsensical for its current semantic usage, then it should not be a primitive type, it should be a proper type with only the sensible functions defined. (As an example, using
int
as ID allows multiplying an ID by an ID...)– Matthieu M.
23 hours ago
Using a primitive is IMHO justified in the cases where there is no validation or other logic depending on this particular type. => I disagree. Even if all values are valid, I would still favor conveying the semantics to the language rather than use primitives. If a function can be called on a primitive type which is nonsensical for its current semantic usage, then it should not be a primitive type, it should be a proper type with only the sensible functions defined. (As an example, using
int
as ID allows multiplying an ID by an ID...)– Matthieu M.
23 hours ago
@R.Schmitz I think ZIP codes are a poor example for the distinction you're making. Something that changes often might be a candidate for separate
Foo
and FooValidator
classes. We could have a ZipCode
class that validates the format and a ZipCodeValidator
that hits some Web service to check that a correctly formatted ZipCode
is actually current. We know that ZIP codes change. But practically, we're going to have a list of valid ZIP codes encapsulated in ZipCode
, or in some local database.– TKK
23 hours ago
@R.Schmitz I think ZIP codes are a poor example for the distinction you're making. Something that changes often might be a candidate for separate
Foo
and FooValidator
classes. We could have a ZipCode
class that validates the format and a ZipCodeValidator
that hits some Web service to check that a correctly formatted ZipCode
is actually current. We know that ZIP codes change. But practically, we're going to have a list of valid ZIP codes encapsulated in ZipCode
, or in some local database.– TKK
23 hours ago
|
show 9 more comments
If can do:
new ZipCode("totally invalid zip code");
And the constructor for ZipCode does:
ZipCodeHelper.validate("totally invalid zip code");
Then you've broken encapsulation, and added a pretty silly dependency to the ZipCode class. If the constructor doesn't call ZipCodeHelper.validate(...)
then you have isolated logic in its own island without actually enforcing it. You can create invalid zip codes.
The validate
method should be a static method on the ZipCode class. Now the knowledge of a "valid" zip code is bundled together with the ZipCode class. Given that your code examples look like Java, the constructor of ZipCode should throw an exception if an incorrect format is given:
public class ZipCode {
private String zipCode;
public ZipCode(string zipCode) {
if (!validate(zipCode))
throw new IllegalFormatException("Invalid zip code");
this.zipCode = zipCode;
}
public static bool validate(String zipCode) {
// logic to check format
}
@Override
public String toString() {
return zipCode;
}
}
The constructor checks the format and throws an exception, thereby preventing invalid zip codes from being created, and the static validate
method is available to other code so the logic of checking the format is encapsulated in the ZipCode class.
There is no "yo-yo" in this variant of the ZipCode class. It's just called proper Object Oriented Programming.
We are also going to ignore internationalization here, which may necessitate another class called ZipCodeFormat or PostalService (e.g. PostalService.isValidPostalCode(...), PostalService.parsePostalCode(...), etc.).
13
Note: The main advantage with @Greg Burkhardt's approach here is that if someone gives you a ZipCode object, you can trust that it contains a valid string without having to check it again, since its type and the fact that it was successfully constructed gives you that guarantee. If you instead passed strings around, you might feel a need to "assert validate(zipCode)" at various places in your code just to be sure that you had a valid zip code, but with a successfully constructed ZipCode object, you can trust that its contents are valid without having to check them again.
– Some Guy
23 hours ago
@SomeGuy While the disadvantage is that you're having a "vexing" exception here. Either you surround every ZipCode creation with a try-catch, or you always check it twice. However, as long as your validation code is minor, this should pose no issue for internal, embedded, game and throwaway apps.
– R. Schmitz
9 hours ago
1
@R.Schmitz: TheZipCode.validate
method is the pre-check that can be performed before invoking a constructor that throws an exception.
– Greg Burghardt
7 hours ago
3
@R.Schmitz: If you are concerned about a vexing exception, an alternate approach to construction is to make the ZipCode constructor private, and provide a public static factory function (Zipcode.create?) that performs validation of the passed-in parameters, returns null if unsuccessful, and otherwise constructs a ZipCode object and returns it. The caller will always have to check for a null return value, of course. On the other hand, if you are in the habit, for instance, of always validating (regex? validate? etc.) before constructing a ZipCode, the exception may not be so vexing in practice.
– Some Guy
6 hours ago
3
A factory function that returns an Optional<ZipCode> is also a possibility. Then the caller has no choice but to explicitly handle possible failure of the factory function. Regardless, in either case, the error will be discovered somewhere near where it was created rather than possibly much later, by client code far from the original problem.
– Some Guy
6 hours ago
|
show 2 more comments
If can do:
new ZipCode("totally invalid zip code");
And the constructor for ZipCode does:
ZipCodeHelper.validate("totally invalid zip code");
Then you've broken encapsulation, and added a pretty silly dependency to the ZipCode class. If the constructor doesn't call ZipCodeHelper.validate(...)
then you have isolated logic in its own island without actually enforcing it. You can create invalid zip codes.
The validate
method should be a static method on the ZipCode class. Now the knowledge of a "valid" zip code is bundled together with the ZipCode class. Given that your code examples look like Java, the constructor of ZipCode should throw an exception if an incorrect format is given:
public class ZipCode {
private String zipCode;
public ZipCode(string zipCode) {
if (!validate(zipCode))
throw new IllegalFormatException("Invalid zip code");
this.zipCode = zipCode;
}
public static bool validate(String zipCode) {
// logic to check format
}
@Override
public String toString() {
return zipCode;
}
}
The constructor checks the format and throws an exception, thereby preventing invalid zip codes from being created, and the static validate
method is available to other code so the logic of checking the format is encapsulated in the ZipCode class.
There is no "yo-yo" in this variant of the ZipCode class. It's just called proper Object Oriented Programming.
We are also going to ignore internationalization here, which may necessitate another class called ZipCodeFormat or PostalService (e.g. PostalService.isValidPostalCode(...), PostalService.parsePostalCode(...), etc.).
13
Note: The main advantage with @Greg Burkhardt's approach here is that if someone gives you a ZipCode object, you can trust that it contains a valid string without having to check it again, since its type and the fact that it was successfully constructed gives you that guarantee. If you instead passed strings around, you might feel a need to "assert validate(zipCode)" at various places in your code just to be sure that you had a valid zip code, but with a successfully constructed ZipCode object, you can trust that its contents are valid without having to check them again.
– Some Guy
23 hours ago
@SomeGuy While the disadvantage is that you're having a "vexing" exception here. Either you surround every ZipCode creation with a try-catch, or you always check it twice. However, as long as your validation code is minor, this should pose no issue for internal, embedded, game and throwaway apps.
– R. Schmitz
9 hours ago
1
@R.Schmitz: TheZipCode.validate
method is the pre-check that can be performed before invoking a constructor that throws an exception.
– Greg Burghardt
7 hours ago
3
@R.Schmitz: If you are concerned about a vexing exception, an alternate approach to construction is to make the ZipCode constructor private, and provide a public static factory function (Zipcode.create?) that performs validation of the passed-in parameters, returns null if unsuccessful, and otherwise constructs a ZipCode object and returns it. The caller will always have to check for a null return value, of course. On the other hand, if you are in the habit, for instance, of always validating (regex? validate? etc.) before constructing a ZipCode, the exception may not be so vexing in practice.
– Some Guy
6 hours ago
3
A factory function that returns an Optional<ZipCode> is also a possibility. Then the caller has no choice but to explicitly handle possible failure of the factory function. Regardless, in either case, the error will be discovered somewhere near where it was created rather than possibly much later, by client code far from the original problem.
– Some Guy
6 hours ago
|
show 2 more comments
If can do:
new ZipCode("totally invalid zip code");
And the constructor for ZipCode does:
ZipCodeHelper.validate("totally invalid zip code");
Then you've broken encapsulation, and added a pretty silly dependency to the ZipCode class. If the constructor doesn't call ZipCodeHelper.validate(...)
then you have isolated logic in its own island without actually enforcing it. You can create invalid zip codes.
The validate
method should be a static method on the ZipCode class. Now the knowledge of a "valid" zip code is bundled together with the ZipCode class. Given that your code examples look like Java, the constructor of ZipCode should throw an exception if an incorrect format is given:
public class ZipCode {
private String zipCode;
public ZipCode(string zipCode) {
if (!validate(zipCode))
throw new IllegalFormatException("Invalid zip code");
this.zipCode = zipCode;
}
public static bool validate(String zipCode) {
// logic to check format
}
@Override
public String toString() {
return zipCode;
}
}
The constructor checks the format and throws an exception, thereby preventing invalid zip codes from being created, and the static validate
method is available to other code so the logic of checking the format is encapsulated in the ZipCode class.
There is no "yo-yo" in this variant of the ZipCode class. It's just called proper Object Oriented Programming.
We are also going to ignore internationalization here, which may necessitate another class called ZipCodeFormat or PostalService (e.g. PostalService.isValidPostalCode(...), PostalService.parsePostalCode(...), etc.).
If can do:
new ZipCode("totally invalid zip code");
And the constructor for ZipCode does:
ZipCodeHelper.validate("totally invalid zip code");
Then you've broken encapsulation, and added a pretty silly dependency to the ZipCode class. If the constructor doesn't call ZipCodeHelper.validate(...)
then you have isolated logic in its own island without actually enforcing it. You can create invalid zip codes.
The validate
method should be a static method on the ZipCode class. Now the knowledge of a "valid" zip code is bundled together with the ZipCode class. Given that your code examples look like Java, the constructor of ZipCode should throw an exception if an incorrect format is given:
public class ZipCode {
private String zipCode;
public ZipCode(string zipCode) {
if (!validate(zipCode))
throw new IllegalFormatException("Invalid zip code");
this.zipCode = zipCode;
}
public static bool validate(String zipCode) {
// logic to check format
}
@Override
public String toString() {
return zipCode;
}
}
The constructor checks the format and throws an exception, thereby preventing invalid zip codes from being created, and the static validate
method is available to other code so the logic of checking the format is encapsulated in the ZipCode class.
There is no "yo-yo" in this variant of the ZipCode class. It's just called proper Object Oriented Programming.
We are also going to ignore internationalization here, which may necessitate another class called ZipCodeFormat or PostalService (e.g. PostalService.isValidPostalCode(...), PostalService.parsePostalCode(...), etc.).
answered yesterday
Greg BurghardtGreg Burghardt
12.7k42957
12.7k42957
13
Note: The main advantage with @Greg Burkhardt's approach here is that if someone gives you a ZipCode object, you can trust that it contains a valid string without having to check it again, since its type and the fact that it was successfully constructed gives you that guarantee. If you instead passed strings around, you might feel a need to "assert validate(zipCode)" at various places in your code just to be sure that you had a valid zip code, but with a successfully constructed ZipCode object, you can trust that its contents are valid without having to check them again.
– Some Guy
23 hours ago
@SomeGuy While the disadvantage is that you're having a "vexing" exception here. Either you surround every ZipCode creation with a try-catch, or you always check it twice. However, as long as your validation code is minor, this should pose no issue for internal, embedded, game and throwaway apps.
– R. Schmitz
9 hours ago
1
@R.Schmitz: TheZipCode.validate
method is the pre-check that can be performed before invoking a constructor that throws an exception.
– Greg Burghardt
7 hours ago
3
@R.Schmitz: If you are concerned about a vexing exception, an alternate approach to construction is to make the ZipCode constructor private, and provide a public static factory function (Zipcode.create?) that performs validation of the passed-in parameters, returns null if unsuccessful, and otherwise constructs a ZipCode object and returns it. The caller will always have to check for a null return value, of course. On the other hand, if you are in the habit, for instance, of always validating (regex? validate? etc.) before constructing a ZipCode, the exception may not be so vexing in practice.
– Some Guy
6 hours ago
3
A factory function that returns an Optional<ZipCode> is also a possibility. Then the caller has no choice but to explicitly handle possible failure of the factory function. Regardless, in either case, the error will be discovered somewhere near where it was created rather than possibly much later, by client code far from the original problem.
– Some Guy
6 hours ago
|
show 2 more comments
13
Note: The main advantage with @Greg Burkhardt's approach here is that if someone gives you a ZipCode object, you can trust that it contains a valid string without having to check it again, since its type and the fact that it was successfully constructed gives you that guarantee. If you instead passed strings around, you might feel a need to "assert validate(zipCode)" at various places in your code just to be sure that you had a valid zip code, but with a successfully constructed ZipCode object, you can trust that its contents are valid without having to check them again.
– Some Guy
23 hours ago
@SomeGuy While the disadvantage is that you're having a "vexing" exception here. Either you surround every ZipCode creation with a try-catch, or you always check it twice. However, as long as your validation code is minor, this should pose no issue for internal, embedded, game and throwaway apps.
– R. Schmitz
9 hours ago
1
@R.Schmitz: TheZipCode.validate
method is the pre-check that can be performed before invoking a constructor that throws an exception.
– Greg Burghardt
7 hours ago
3
@R.Schmitz: If you are concerned about a vexing exception, an alternate approach to construction is to make the ZipCode constructor private, and provide a public static factory function (Zipcode.create?) that performs validation of the passed-in parameters, returns null if unsuccessful, and otherwise constructs a ZipCode object and returns it. The caller will always have to check for a null return value, of course. On the other hand, if you are in the habit, for instance, of always validating (regex? validate? etc.) before constructing a ZipCode, the exception may not be so vexing in practice.
– Some Guy
6 hours ago
3
A factory function that returns an Optional<ZipCode> is also a possibility. Then the caller has no choice but to explicitly handle possible failure of the factory function. Regardless, in either case, the error will be discovered somewhere near where it was created rather than possibly much later, by client code far from the original problem.
– Some Guy
6 hours ago
13
13
Note: The main advantage with @Greg Burkhardt's approach here is that if someone gives you a ZipCode object, you can trust that it contains a valid string without having to check it again, since its type and the fact that it was successfully constructed gives you that guarantee. If you instead passed strings around, you might feel a need to "assert validate(zipCode)" at various places in your code just to be sure that you had a valid zip code, but with a successfully constructed ZipCode object, you can trust that its contents are valid without having to check them again.
– Some Guy
23 hours ago
Note: The main advantage with @Greg Burkhardt's approach here is that if someone gives you a ZipCode object, you can trust that it contains a valid string without having to check it again, since its type and the fact that it was successfully constructed gives you that guarantee. If you instead passed strings around, you might feel a need to "assert validate(zipCode)" at various places in your code just to be sure that you had a valid zip code, but with a successfully constructed ZipCode object, you can trust that its contents are valid without having to check them again.
– Some Guy
23 hours ago
@SomeGuy While the disadvantage is that you're having a "vexing" exception here. Either you surround every ZipCode creation with a try-catch, or you always check it twice. However, as long as your validation code is minor, this should pose no issue for internal, embedded, game and throwaway apps.
– R. Schmitz
9 hours ago
@SomeGuy While the disadvantage is that you're having a "vexing" exception here. Either you surround every ZipCode creation with a try-catch, or you always check it twice. However, as long as your validation code is minor, this should pose no issue for internal, embedded, game and throwaway apps.
– R. Schmitz
9 hours ago
1
1
@R.Schmitz: The
ZipCode.validate
method is the pre-check that can be performed before invoking a constructor that throws an exception.– Greg Burghardt
7 hours ago
@R.Schmitz: The
ZipCode.validate
method is the pre-check that can be performed before invoking a constructor that throws an exception.– Greg Burghardt
7 hours ago
3
3
@R.Schmitz: If you are concerned about a vexing exception, an alternate approach to construction is to make the ZipCode constructor private, and provide a public static factory function (Zipcode.create?) that performs validation of the passed-in parameters, returns null if unsuccessful, and otherwise constructs a ZipCode object and returns it. The caller will always have to check for a null return value, of course. On the other hand, if you are in the habit, for instance, of always validating (regex? validate? etc.) before constructing a ZipCode, the exception may not be so vexing in practice.
– Some Guy
6 hours ago
@R.Schmitz: If you are concerned about a vexing exception, an alternate approach to construction is to make the ZipCode constructor private, and provide a public static factory function (Zipcode.create?) that performs validation of the passed-in parameters, returns null if unsuccessful, and otherwise constructs a ZipCode object and returns it. The caller will always have to check for a null return value, of course. On the other hand, if you are in the habit, for instance, of always validating (regex? validate? etc.) before constructing a ZipCode, the exception may not be so vexing in practice.
– Some Guy
6 hours ago
3
3
A factory function that returns an Optional<ZipCode> is also a possibility. Then the caller has no choice but to explicitly handle possible failure of the factory function. Regardless, in either case, the error will be discovered somewhere near where it was created rather than possibly much later, by client code far from the original problem.
– Some Guy
6 hours ago
A factory function that returns an Optional<ZipCode> is also a possibility. Then the caller has no choice but to explicitly handle possible failure of the factory function. Regardless, in either case, the error will be discovered somewhere near where it was created rather than possibly much later, by client code far from the original problem.
– Some Guy
6 hours ago
|
show 2 more comments
If you wrestle a lot with this question, perhaps the language you use is not the right tool for the job?
This kind of "domain-typed primitives" are trivially easy to express in, for example, F#.
There you could, for example, write:
type ZipCode = string
type Town = string
type Adress = {
zipCode: ZipCode
town: Town
//etc
}
let adress1 = {
zipCode = ZipCode "90210"
town = Town "Beverly Hills"
}
let faultyAdress = {
zipCode = "12345" // <-Compiler error
town = adress1.zipCode // <- Compiler error
}
This is really useful for avoiding common mistakes, like comparing id's of different entities. And since these typed primitives are much more lightweight than a C# or Java-class, you'll end up actually use them.
Interesting - how would it look like if you wanted to enforce validation ofZipCode
?
– Hulk
11 hours ago
2
@Hulk You can write OO-style in F# and make the types into classes. However, I prefer functional style, declaring the type with type ZipCode = private ZipCode of string and adding a ZipCode module with a create function. There are some examples here: gist.github.com/swlaschin/54cfff886669ccab895a
– Guran
11 hours ago
add a comment |
If you wrestle a lot with this question, perhaps the language you use is not the right tool for the job?
This kind of "domain-typed primitives" are trivially easy to express in, for example, F#.
There you could, for example, write:
type ZipCode = string
type Town = string
type Adress = {
zipCode: ZipCode
town: Town
//etc
}
let adress1 = {
zipCode = ZipCode "90210"
town = Town "Beverly Hills"
}
let faultyAdress = {
zipCode = "12345" // <-Compiler error
town = adress1.zipCode // <- Compiler error
}
This is really useful for avoiding common mistakes, like comparing id's of different entities. And since these typed primitives are much more lightweight than a C# or Java-class, you'll end up actually use them.
Interesting - how would it look like if you wanted to enforce validation ofZipCode
?
– Hulk
11 hours ago
2
@Hulk You can write OO-style in F# and make the types into classes. However, I prefer functional style, declaring the type with type ZipCode = private ZipCode of string and adding a ZipCode module with a create function. There are some examples here: gist.github.com/swlaschin/54cfff886669ccab895a
– Guran
11 hours ago
add a comment |
If you wrestle a lot with this question, perhaps the language you use is not the right tool for the job?
This kind of "domain-typed primitives" are trivially easy to express in, for example, F#.
There you could, for example, write:
type ZipCode = string
type Town = string
type Adress = {
zipCode: ZipCode
town: Town
//etc
}
let adress1 = {
zipCode = ZipCode "90210"
town = Town "Beverly Hills"
}
let faultyAdress = {
zipCode = "12345" // <-Compiler error
town = adress1.zipCode // <- Compiler error
}
This is really useful for avoiding common mistakes, like comparing id's of different entities. And since these typed primitives are much more lightweight than a C# or Java-class, you'll end up actually use them.
If you wrestle a lot with this question, perhaps the language you use is not the right tool for the job?
This kind of "domain-typed primitives" are trivially easy to express in, for example, F#.
There you could, for example, write:
type ZipCode = string
type Town = string
type Adress = {
zipCode: ZipCode
town: Town
//etc
}
let adress1 = {
zipCode = ZipCode "90210"
town = Town "Beverly Hills"
}
let faultyAdress = {
zipCode = "12345" // <-Compiler error
town = adress1.zipCode // <- Compiler error
}
This is really useful for avoiding common mistakes, like comparing id's of different entities. And since these typed primitives are much more lightweight than a C# or Java-class, you'll end up actually use them.
answered 11 hours ago
GuranGuran
1866
1866
Interesting - how would it look like if you wanted to enforce validation ofZipCode
?
– Hulk
11 hours ago
2
@Hulk You can write OO-style in F# and make the types into classes. However, I prefer functional style, declaring the type with type ZipCode = private ZipCode of string and adding a ZipCode module with a create function. There are some examples here: gist.github.com/swlaschin/54cfff886669ccab895a
– Guran
11 hours ago
add a comment |
Interesting - how would it look like if you wanted to enforce validation ofZipCode
?
– Hulk
11 hours ago
2
@Hulk You can write OO-style in F# and make the types into classes. However, I prefer functional style, declaring the type with type ZipCode = private ZipCode of string and adding a ZipCode module with a create function. There are some examples here: gist.github.com/swlaschin/54cfff886669ccab895a
– Guran
11 hours ago
Interesting - how would it look like if you wanted to enforce validation of
ZipCode
?– Hulk
11 hours ago
Interesting - how would it look like if you wanted to enforce validation of
ZipCode
?– Hulk
11 hours ago
2
2
@Hulk You can write OO-style in F# and make the types into classes. However, I prefer functional style, declaring the type with type ZipCode = private ZipCode of string and adding a ZipCode module with a create function. There are some examples here: gist.github.com/swlaschin/54cfff886669ccab895a
– Guran
11 hours ago
@Hulk You can write OO-style in F# and make the types into classes. However, I prefer functional style, declaring the type with type ZipCode = private ZipCode of string and adding a ZipCode module with a create function. There are some examples here: gist.github.com/swlaschin/54cfff886669ccab895a
– Guran
11 hours ago
add a comment |
The ZipCode
abstraction could only make sense if your Address
class did not also have a TownName
property. Otherwise, you have half an abstraction: the zip code designates the town, but these two related bits of information are found in different classes. It doesn't quite make sense.
However, even then, it's still not a correct application (or rather solution to) primitive obsession; which, as I understand it, mainly focuses on two things:
- Using primitives as the input (or even output) values of a method, especially when a collection of primitives is needed.
- Classes that grow extra properties over time without ever reconsidering whether some of these should be grouped into a subclass of their own.
Your case is neither. An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country, ...). There is little to no reason to break up this data as it has a single responsibility: designate a location on Earth. An address requires all of these fields in order to be meaningful. Half an address is pointless.
This is how you know that you don't need to subdivide any further: breaking it down any further would detract from the functional intention of the Address
class. Similarly, you don't need a Name
subclass to be used in the Person
class, unless Name
(without a person attached) is a meaningful concept in your domain. Which it (usually) isn't. Names are used for identifying people, they usually have no value on their own.
1
@RikD: From the answer: "you don't need aName
subclass to be used in thePerson
class, unless Name (without a person attached) is a meaningful concept in your domain." When you have custom validation for the names, the name has then become a meaningful concept in your domain; which I explicitly mentioned as a valid use case for using a subtype. Secondly, for the zipcode validation, you're introducing additional assumptions, such as zip codes needing to follow a given country's format. You're broaching a topic that's much broader than the intent of OP's question.
– Flater
yesterday
4
"An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country)." - Well that's just plain wrong. For a good way to deal with this, look at Amazon's address form.
– R. Schmitz
yesterday
1
@R.Schmitz The format of an address is different from the properties it has. Yes, culturally it can be formatted different ways, but the property declaration is not responsible for making that decision. It only defines which fields are possible. My answere did not address address validation, which your linked page is mostly about (that and formats, also not my focus). Additionally, not every culture has these wildly varying address formats (Belgium and the Netherlands have a rather fixed format for all listed cases). Additionally, not every application is built to be used internationally.
– Flater
yesterday
3
@Flater Well I won't blame you for not reading the full list of falsehoods, because it's quite long, but it literally contains "Addresses will have a street", "An address require both a city and a country", "An address will have a postcode" etc., which is contrary to what the quoted sentence says.
– R. Schmitz
yesterday
7
@GregBurghardt "Zip code assumes United States Postal Service, and you can derive the name of the town from the zip code. Cities can have multiple zip codes, but each zip code is only tied to 1 city." This is not correct in general. I have a zipcode that is used mainly for a neighboring city but my residence is not located there. Zipcodes do not always align with governmental boundaries. For example 42223 contains counties from both TN and KY.
– JimmyJames
yesterday
|
show 12 more comments
The ZipCode
abstraction could only make sense if your Address
class did not also have a TownName
property. Otherwise, you have half an abstraction: the zip code designates the town, but these two related bits of information are found in different classes. It doesn't quite make sense.
However, even then, it's still not a correct application (or rather solution to) primitive obsession; which, as I understand it, mainly focuses on two things:
- Using primitives as the input (or even output) values of a method, especially when a collection of primitives is needed.
- Classes that grow extra properties over time without ever reconsidering whether some of these should be grouped into a subclass of their own.
Your case is neither. An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country, ...). There is little to no reason to break up this data as it has a single responsibility: designate a location on Earth. An address requires all of these fields in order to be meaningful. Half an address is pointless.
This is how you know that you don't need to subdivide any further: breaking it down any further would detract from the functional intention of the Address
class. Similarly, you don't need a Name
subclass to be used in the Person
class, unless Name
(without a person attached) is a meaningful concept in your domain. Which it (usually) isn't. Names are used for identifying people, they usually have no value on their own.
1
@RikD: From the answer: "you don't need aName
subclass to be used in thePerson
class, unless Name (without a person attached) is a meaningful concept in your domain." When you have custom validation for the names, the name has then become a meaningful concept in your domain; which I explicitly mentioned as a valid use case for using a subtype. Secondly, for the zipcode validation, you're introducing additional assumptions, such as zip codes needing to follow a given country's format. You're broaching a topic that's much broader than the intent of OP's question.
– Flater
yesterday
4
"An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country)." - Well that's just plain wrong. For a good way to deal with this, look at Amazon's address form.
– R. Schmitz
yesterday
1
@R.Schmitz The format of an address is different from the properties it has. Yes, culturally it can be formatted different ways, but the property declaration is not responsible for making that decision. It only defines which fields are possible. My answere did not address address validation, which your linked page is mostly about (that and formats, also not my focus). Additionally, not every culture has these wildly varying address formats (Belgium and the Netherlands have a rather fixed format for all listed cases). Additionally, not every application is built to be used internationally.
– Flater
yesterday
3
@Flater Well I won't blame you for not reading the full list of falsehoods, because it's quite long, but it literally contains "Addresses will have a street", "An address require both a city and a country", "An address will have a postcode" etc., which is contrary to what the quoted sentence says.
– R. Schmitz
yesterday
7
@GregBurghardt "Zip code assumes United States Postal Service, and you can derive the name of the town from the zip code. Cities can have multiple zip codes, but each zip code is only tied to 1 city." This is not correct in general. I have a zipcode that is used mainly for a neighboring city but my residence is not located there. Zipcodes do not always align with governmental boundaries. For example 42223 contains counties from both TN and KY.
– JimmyJames
yesterday
|
show 12 more comments
The ZipCode
abstraction could only make sense if your Address
class did not also have a TownName
property. Otherwise, you have half an abstraction: the zip code designates the town, but these two related bits of information are found in different classes. It doesn't quite make sense.
However, even then, it's still not a correct application (or rather solution to) primitive obsession; which, as I understand it, mainly focuses on two things:
- Using primitives as the input (or even output) values of a method, especially when a collection of primitives is needed.
- Classes that grow extra properties over time without ever reconsidering whether some of these should be grouped into a subclass of their own.
Your case is neither. An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country, ...). There is little to no reason to break up this data as it has a single responsibility: designate a location on Earth. An address requires all of these fields in order to be meaningful. Half an address is pointless.
This is how you know that you don't need to subdivide any further: breaking it down any further would detract from the functional intention of the Address
class. Similarly, you don't need a Name
subclass to be used in the Person
class, unless Name
(without a person attached) is a meaningful concept in your domain. Which it (usually) isn't. Names are used for identifying people, they usually have no value on their own.
The ZipCode
abstraction could only make sense if your Address
class did not also have a TownName
property. Otherwise, you have half an abstraction: the zip code designates the town, but these two related bits of information are found in different classes. It doesn't quite make sense.
However, even then, it's still not a correct application (or rather solution to) primitive obsession; which, as I understand it, mainly focuses on two things:
- Using primitives as the input (or even output) values of a method, especially when a collection of primitives is needed.
- Classes that grow extra properties over time without ever reconsidering whether some of these should be grouped into a subclass of their own.
Your case is neither. An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country, ...). There is little to no reason to break up this data as it has a single responsibility: designate a location on Earth. An address requires all of these fields in order to be meaningful. Half an address is pointless.
This is how you know that you don't need to subdivide any further: breaking it down any further would detract from the functional intention of the Address
class. Similarly, you don't need a Name
subclass to be used in the Person
class, unless Name
(without a person attached) is a meaningful concept in your domain. Which it (usually) isn't. Names are used for identifying people, they usually have no value on their own.
edited yesterday
answered yesterday
FlaterFlater
6,97621323
6,97621323
1
@RikD: From the answer: "you don't need aName
subclass to be used in thePerson
class, unless Name (without a person attached) is a meaningful concept in your domain." When you have custom validation for the names, the name has then become a meaningful concept in your domain; which I explicitly mentioned as a valid use case for using a subtype. Secondly, for the zipcode validation, you're introducing additional assumptions, such as zip codes needing to follow a given country's format. You're broaching a topic that's much broader than the intent of OP's question.
– Flater
yesterday
4
"An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country)." - Well that's just plain wrong. For a good way to deal with this, look at Amazon's address form.
– R. Schmitz
yesterday
1
@R.Schmitz The format of an address is different from the properties it has. Yes, culturally it can be formatted different ways, but the property declaration is not responsible for making that decision. It only defines which fields are possible. My answere did not address address validation, which your linked page is mostly about (that and formats, also not my focus). Additionally, not every culture has these wildly varying address formats (Belgium and the Netherlands have a rather fixed format for all listed cases). Additionally, not every application is built to be used internationally.
– Flater
yesterday
3
@Flater Well I won't blame you for not reading the full list of falsehoods, because it's quite long, but it literally contains "Addresses will have a street", "An address require both a city and a country", "An address will have a postcode" etc., which is contrary to what the quoted sentence says.
– R. Schmitz
yesterday
7
@GregBurghardt "Zip code assumes United States Postal Service, and you can derive the name of the town from the zip code. Cities can have multiple zip codes, but each zip code is only tied to 1 city." This is not correct in general. I have a zipcode that is used mainly for a neighboring city but my residence is not located there. Zipcodes do not always align with governmental boundaries. For example 42223 contains counties from both TN and KY.
– JimmyJames
yesterday
|
show 12 more comments
1
@RikD: From the answer: "you don't need aName
subclass to be used in thePerson
class, unless Name (without a person attached) is a meaningful concept in your domain." When you have custom validation for the names, the name has then become a meaningful concept in your domain; which I explicitly mentioned as a valid use case for using a subtype. Secondly, for the zipcode validation, you're introducing additional assumptions, such as zip codes needing to follow a given country's format. You're broaching a topic that's much broader than the intent of OP's question.
– Flater
yesterday
4
"An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country)." - Well that's just plain wrong. For a good way to deal with this, look at Amazon's address form.
– R. Schmitz
yesterday
1
@R.Schmitz The format of an address is different from the properties it has. Yes, culturally it can be formatted different ways, but the property declaration is not responsible for making that decision. It only defines which fields are possible. My answere did not address address validation, which your linked page is mostly about (that and formats, also not my focus). Additionally, not every culture has these wildly varying address formats (Belgium and the Netherlands have a rather fixed format for all listed cases). Additionally, not every application is built to be used internationally.
– Flater
yesterday
3
@Flater Well I won't blame you for not reading the full list of falsehoods, because it's quite long, but it literally contains "Addresses will have a street", "An address require both a city and a country", "An address will have a postcode" etc., which is contrary to what the quoted sentence says.
– R. Schmitz
yesterday
7
@GregBurghardt "Zip code assumes United States Postal Service, and you can derive the name of the town from the zip code. Cities can have multiple zip codes, but each zip code is only tied to 1 city." This is not correct in general. I have a zipcode that is used mainly for a neighboring city but my residence is not located there. Zipcodes do not always align with governmental boundaries. For example 42223 contains counties from both TN and KY.
– JimmyJames
yesterday
1
1
@RikD: From the answer: "you don't need a
Name
subclass to be used in the Person
class, unless Name (without a person attached) is a meaningful concept in your domain." When you have custom validation for the names, the name has then become a meaningful concept in your domain; which I explicitly mentioned as a valid use case for using a subtype. Secondly, for the zipcode validation, you're introducing additional assumptions, such as zip codes needing to follow a given country's format. You're broaching a topic that's much broader than the intent of OP's question.– Flater
yesterday
@RikD: From the answer: "you don't need a
Name
subclass to be used in the Person
class, unless Name (without a person attached) is a meaningful concept in your domain." When you have custom validation for the names, the name has then become a meaningful concept in your domain; which I explicitly mentioned as a valid use case for using a subtype. Secondly, for the zipcode validation, you're introducing additional assumptions, such as zip codes needing to follow a given country's format. You're broaching a topic that's much broader than the intent of OP's question.– Flater
yesterday
4
4
"An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country)." - Well that's just plain wrong. For a good way to deal with this, look at Amazon's address form.
– R. Schmitz
yesterday
"An address is a well-defined concept with clearly necessary properties (street, number, zip, town, state, country)." - Well that's just plain wrong. For a good way to deal with this, look at Amazon's address form.
– R. Schmitz
yesterday
1
1
@R.Schmitz The format of an address is different from the properties it has. Yes, culturally it can be formatted different ways, but the property declaration is not responsible for making that decision. It only defines which fields are possible. My answere did not address address validation, which your linked page is mostly about (that and formats, also not my focus). Additionally, not every culture has these wildly varying address formats (Belgium and the Netherlands have a rather fixed format for all listed cases). Additionally, not every application is built to be used internationally.
– Flater
yesterday
@R.Schmitz The format of an address is different from the properties it has. Yes, culturally it can be formatted different ways, but the property declaration is not responsible for making that decision. It only defines which fields are possible. My answere did not address address validation, which your linked page is mostly about (that and formats, also not my focus). Additionally, not every culture has these wildly varying address formats (Belgium and the Netherlands have a rather fixed format for all listed cases). Additionally, not every application is built to be used internationally.
– Flater
yesterday
3
3
@Flater Well I won't blame you for not reading the full list of falsehoods, because it's quite long, but it literally contains "Addresses will have a street", "An address require both a city and a country", "An address will have a postcode" etc., which is contrary to what the quoted sentence says.
– R. Schmitz
yesterday
@Flater Well I won't blame you for not reading the full list of falsehoods, because it's quite long, but it literally contains "Addresses will have a street", "An address require both a city and a country", "An address will have a postcode" etc., which is contrary to what the quoted sentence says.
– R. Schmitz
yesterday
7
7
@GregBurghardt "Zip code assumes United States Postal Service, and you can derive the name of the town from the zip code. Cities can have multiple zip codes, but each zip code is only tied to 1 city." This is not correct in general. I have a zipcode that is used mainly for a neighboring city but my residence is not located there. Zipcodes do not always align with governmental boundaries. For example 42223 contains counties from both TN and KY.
– JimmyJames
yesterday
@GregBurghardt "Zip code assumes United States Postal Service, and you can derive the name of the town from the zip code. Cities can have multiple zip codes, but each zip code is only tied to 1 city." This is not correct in general. I have a zipcode that is used mainly for a neighboring city but my residence is not located there. Zipcodes do not always align with governmental boundaries. For example 42223 contains counties from both TN and KY.
– JimmyJames
yesterday
|
show 12 more comments
The answer depends entirely on what you actually want to do with the ZIP codes. Here are two extreme possibilities:
(1) All addresses are guaranteed to be in a single country. No exceptions at all. (E.g. no foreign customers, or no employees whose private address is abroad while they are working for a foreign customer.) This country has ZIP codes and they can be expected to never be seriously problematic (i.e. they don't require free-form input such as "currently D4B 6N2, but this changes every 2 weeks"). The ZIP codes are used not just for addressing, but for validation of payment information or similar purposes. - Under these circumstances, a ZIP code class makes a lot of sense.
(2) Addresses can be in almost every country, so dozens or hundreds of addressing schemes with or without ZIP codes (and with thousands of weird exceptions and special cases) are relevant. A "ZIP" code is really only asked for to remind people from countries where ZIP codes are used not to forget to provide theirs. The addresses are only used so that if someone loses access to their account and they can prove their name and address, access will be restored. - Under these circumstances, ZIP code classes for all relevant countries would be an enormous effort. Fortunately they are not needed at all.
add a comment |
The answer depends entirely on what you actually want to do with the ZIP codes. Here are two extreme possibilities:
(1) All addresses are guaranteed to be in a single country. No exceptions at all. (E.g. no foreign customers, or no employees whose private address is abroad while they are working for a foreign customer.) This country has ZIP codes and they can be expected to never be seriously problematic (i.e. they don't require free-form input such as "currently D4B 6N2, but this changes every 2 weeks"). The ZIP codes are used not just for addressing, but for validation of payment information or similar purposes. - Under these circumstances, a ZIP code class makes a lot of sense.
(2) Addresses can be in almost every country, so dozens or hundreds of addressing schemes with or without ZIP codes (and with thousands of weird exceptions and special cases) are relevant. A "ZIP" code is really only asked for to remind people from countries where ZIP codes are used not to forget to provide theirs. The addresses are only used so that if someone loses access to their account and they can prove their name and address, access will be restored. - Under these circumstances, ZIP code classes for all relevant countries would be an enormous effort. Fortunately they are not needed at all.
add a comment |
The answer depends entirely on what you actually want to do with the ZIP codes. Here are two extreme possibilities:
(1) All addresses are guaranteed to be in a single country. No exceptions at all. (E.g. no foreign customers, or no employees whose private address is abroad while they are working for a foreign customer.) This country has ZIP codes and they can be expected to never be seriously problematic (i.e. they don't require free-form input such as "currently D4B 6N2, but this changes every 2 weeks"). The ZIP codes are used not just for addressing, but for validation of payment information or similar purposes. - Under these circumstances, a ZIP code class makes a lot of sense.
(2) Addresses can be in almost every country, so dozens or hundreds of addressing schemes with or without ZIP codes (and with thousands of weird exceptions and special cases) are relevant. A "ZIP" code is really only asked for to remind people from countries where ZIP codes are used not to forget to provide theirs. The addresses are only used so that if someone loses access to their account and they can prove their name and address, access will be restored. - Under these circumstances, ZIP code classes for all relevant countries would be an enormous effort. Fortunately they are not needed at all.
The answer depends entirely on what you actually want to do with the ZIP codes. Here are two extreme possibilities:
(1) All addresses are guaranteed to be in a single country. No exceptions at all. (E.g. no foreign customers, or no employees whose private address is abroad while they are working for a foreign customer.) This country has ZIP codes and they can be expected to never be seriously problematic (i.e. they don't require free-form input such as "currently D4B 6N2, but this changes every 2 weeks"). The ZIP codes are used not just for addressing, but for validation of payment information or similar purposes. - Under these circumstances, a ZIP code class makes a lot of sense.
(2) Addresses can be in almost every country, so dozens or hundreds of addressing schemes with or without ZIP codes (and with thousands of weird exceptions and special cases) are relevant. A "ZIP" code is really only asked for to remind people from countries where ZIP codes are used not to forget to provide theirs. The addresses are only used so that if someone loses access to their account and they can prove their name and address, access will be restored. - Under these circumstances, ZIP code classes for all relevant countries would be an enormous effort. Fortunately they are not needed at all.
answered 1 hour ago
Hans AdlerHans Adler
18715
18715
add a comment |
add a comment |
The other answers have talked about OO domain modelling and using a richer type to represent your value.
I don't disagree, especially given the example code you posted.
But I also wonder if that actually answers the title of your question.
Consider the following scenario (pulled from an actual project I'm working on):
You have a remote application on a field device that talks to your central server. One of the DB fields for the device entry is a zip code for the address that the field device is at. You don't care about the zip code (or any of the rest of the address for that matter). All of the people who care about it are on the other side of an HTTP boundary: you just happen to be the single source of truth for the data. It has no place in your domain modeling. You just record it, validate it, store it, and on request shuffle it off in a JSON blob to points elsewhere.
In this scenario, doing much of anything beyond validating the insert with an SQL regex constraint (or its ORM equivalent) is probably overkill of the YAGNI variety.
5
Your SQL regex constraint could be viewed as a qualified type - within your database, the Zip code is not stored as "VarChar" but "VarChar constrained by this rule". In some DBMSes, you could easily give that type+constraint a name as a reusable "domain type", and we are back in the recommended place of giving the data a meaningful type. I agree with your answer in principle, but don't think that example matches; a better example would be if your data is "raw sensor data", and the most meaningful type is "byte array" because you have no idea what the data means.
– IMSoP
yesterday
@IMSoP interesting point. Not sure I agree though: you could validate a string zip code in Java (or any other language) with a regex but still be dealing with it as a string instead of a richer type. Depending on the domain logic, further manipulation might be required (for instance ensuring that the zip code matches the state, something that would be difficult/impossible to validate with regex).
– Jared Smith
yesterday
You could, but as soon as you do so, you are ascribing it some domain-specific behaviour, and that is exactly what the quoted articles are saying should lead to the creation of a custom type. The question is not whether you can do it in one style or the other, but whether you should, assuming your programming language gives you the choice.
– IMSoP
yesterday
You could model such a thing as aRegexValidatedString
, containing the string itself and the regex used to validate it. But unless every instance has a unique regex (which is possible but unlikely) this seems a bit silly and wasteful of memory (and possibly regex compilation time). So you either put the regex into a separate table and leave behind a lookup key in each instance to find it (which is arguably worse due to indirection) or you find some way to store it once for each common type of value sharing that rule -- eg. a static field on a domain type, or equivalent method, as IMSoP said.
– Miral
16 hours ago
add a comment |
The other answers have talked about OO domain modelling and using a richer type to represent your value.
I don't disagree, especially given the example code you posted.
But I also wonder if that actually answers the title of your question.
Consider the following scenario (pulled from an actual project I'm working on):
You have a remote application on a field device that talks to your central server. One of the DB fields for the device entry is a zip code for the address that the field device is at. You don't care about the zip code (or any of the rest of the address for that matter). All of the people who care about it are on the other side of an HTTP boundary: you just happen to be the single source of truth for the data. It has no place in your domain modeling. You just record it, validate it, store it, and on request shuffle it off in a JSON blob to points elsewhere.
In this scenario, doing much of anything beyond validating the insert with an SQL regex constraint (or its ORM equivalent) is probably overkill of the YAGNI variety.
5
Your SQL regex constraint could be viewed as a qualified type - within your database, the Zip code is not stored as "VarChar" but "VarChar constrained by this rule". In some DBMSes, you could easily give that type+constraint a name as a reusable "domain type", and we are back in the recommended place of giving the data a meaningful type. I agree with your answer in principle, but don't think that example matches; a better example would be if your data is "raw sensor data", and the most meaningful type is "byte array" because you have no idea what the data means.
– IMSoP
yesterday
@IMSoP interesting point. Not sure I agree though: you could validate a string zip code in Java (or any other language) with a regex but still be dealing with it as a string instead of a richer type. Depending on the domain logic, further manipulation might be required (for instance ensuring that the zip code matches the state, something that would be difficult/impossible to validate with regex).
– Jared Smith
yesterday
You could, but as soon as you do so, you are ascribing it some domain-specific behaviour, and that is exactly what the quoted articles are saying should lead to the creation of a custom type. The question is not whether you can do it in one style or the other, but whether you should, assuming your programming language gives you the choice.
– IMSoP
yesterday
You could model such a thing as aRegexValidatedString
, containing the string itself and the regex used to validate it. But unless every instance has a unique regex (which is possible but unlikely) this seems a bit silly and wasteful of memory (and possibly regex compilation time). So you either put the regex into a separate table and leave behind a lookup key in each instance to find it (which is arguably worse due to indirection) or you find some way to store it once for each common type of value sharing that rule -- eg. a static field on a domain type, or equivalent method, as IMSoP said.
– Miral
16 hours ago
add a comment |
The other answers have talked about OO domain modelling and using a richer type to represent your value.
I don't disagree, especially given the example code you posted.
But I also wonder if that actually answers the title of your question.
Consider the following scenario (pulled from an actual project I'm working on):
You have a remote application on a field device that talks to your central server. One of the DB fields for the device entry is a zip code for the address that the field device is at. You don't care about the zip code (or any of the rest of the address for that matter). All of the people who care about it are on the other side of an HTTP boundary: you just happen to be the single source of truth for the data. It has no place in your domain modeling. You just record it, validate it, store it, and on request shuffle it off in a JSON blob to points elsewhere.
In this scenario, doing much of anything beyond validating the insert with an SQL regex constraint (or its ORM equivalent) is probably overkill of the YAGNI variety.
The other answers have talked about OO domain modelling and using a richer type to represent your value.
I don't disagree, especially given the example code you posted.
But I also wonder if that actually answers the title of your question.
Consider the following scenario (pulled from an actual project I'm working on):
You have a remote application on a field device that talks to your central server. One of the DB fields for the device entry is a zip code for the address that the field device is at. You don't care about the zip code (or any of the rest of the address for that matter). All of the people who care about it are on the other side of an HTTP boundary: you just happen to be the single source of truth for the data. It has no place in your domain modeling. You just record it, validate it, store it, and on request shuffle it off in a JSON blob to points elsewhere.
In this scenario, doing much of anything beyond validating the insert with an SQL regex constraint (or its ORM equivalent) is probably overkill of the YAGNI variety.
answered yesterday
Jared SmithJared Smith
1,217517
1,217517
5
Your SQL regex constraint could be viewed as a qualified type - within your database, the Zip code is not stored as "VarChar" but "VarChar constrained by this rule". In some DBMSes, you could easily give that type+constraint a name as a reusable "domain type", and we are back in the recommended place of giving the data a meaningful type. I agree with your answer in principle, but don't think that example matches; a better example would be if your data is "raw sensor data", and the most meaningful type is "byte array" because you have no idea what the data means.
– IMSoP
yesterday
@IMSoP interesting point. Not sure I agree though: you could validate a string zip code in Java (or any other language) with a regex but still be dealing with it as a string instead of a richer type. Depending on the domain logic, further manipulation might be required (for instance ensuring that the zip code matches the state, something that would be difficult/impossible to validate with regex).
– Jared Smith
yesterday
You could, but as soon as you do so, you are ascribing it some domain-specific behaviour, and that is exactly what the quoted articles are saying should lead to the creation of a custom type. The question is not whether you can do it in one style or the other, but whether you should, assuming your programming language gives you the choice.
– IMSoP
yesterday
You could model such a thing as aRegexValidatedString
, containing the string itself and the regex used to validate it. But unless every instance has a unique regex (which is possible but unlikely) this seems a bit silly and wasteful of memory (and possibly regex compilation time). So you either put the regex into a separate table and leave behind a lookup key in each instance to find it (which is arguably worse due to indirection) or you find some way to store it once for each common type of value sharing that rule -- eg. a static field on a domain type, or equivalent method, as IMSoP said.
– Miral
16 hours ago
add a comment |
5
Your SQL regex constraint could be viewed as a qualified type - within your database, the Zip code is not stored as "VarChar" but "VarChar constrained by this rule". In some DBMSes, you could easily give that type+constraint a name as a reusable "domain type", and we are back in the recommended place of giving the data a meaningful type. I agree with your answer in principle, but don't think that example matches; a better example would be if your data is "raw sensor data", and the most meaningful type is "byte array" because you have no idea what the data means.
– IMSoP
yesterday
@IMSoP interesting point. Not sure I agree though: you could validate a string zip code in Java (or any other language) with a regex but still be dealing with it as a string instead of a richer type. Depending on the domain logic, further manipulation might be required (for instance ensuring that the zip code matches the state, something that would be difficult/impossible to validate with regex).
– Jared Smith
yesterday
You could, but as soon as you do so, you are ascribing it some domain-specific behaviour, and that is exactly what the quoted articles are saying should lead to the creation of a custom type. The question is not whether you can do it in one style or the other, but whether you should, assuming your programming language gives you the choice.
– IMSoP
yesterday
You could model such a thing as aRegexValidatedString
, containing the string itself and the regex used to validate it. But unless every instance has a unique regex (which is possible but unlikely) this seems a bit silly and wasteful of memory (and possibly regex compilation time). So you either put the regex into a separate table and leave behind a lookup key in each instance to find it (which is arguably worse due to indirection) or you find some way to store it once for each common type of value sharing that rule -- eg. a static field on a domain type, or equivalent method, as IMSoP said.
– Miral
16 hours ago
5
5
Your SQL regex constraint could be viewed as a qualified type - within your database, the Zip code is not stored as "VarChar" but "VarChar constrained by this rule". In some DBMSes, you could easily give that type+constraint a name as a reusable "domain type", and we are back in the recommended place of giving the data a meaningful type. I agree with your answer in principle, but don't think that example matches; a better example would be if your data is "raw sensor data", and the most meaningful type is "byte array" because you have no idea what the data means.
– IMSoP
yesterday
Your SQL regex constraint could be viewed as a qualified type - within your database, the Zip code is not stored as "VarChar" but "VarChar constrained by this rule". In some DBMSes, you could easily give that type+constraint a name as a reusable "domain type", and we are back in the recommended place of giving the data a meaningful type. I agree with your answer in principle, but don't think that example matches; a better example would be if your data is "raw sensor data", and the most meaningful type is "byte array" because you have no idea what the data means.
– IMSoP
yesterday
@IMSoP interesting point. Not sure I agree though: you could validate a string zip code in Java (or any other language) with a regex but still be dealing with it as a string instead of a richer type. Depending on the domain logic, further manipulation might be required (for instance ensuring that the zip code matches the state, something that would be difficult/impossible to validate with regex).
– Jared Smith
yesterday
@IMSoP interesting point. Not sure I agree though: you could validate a string zip code in Java (or any other language) with a regex but still be dealing with it as a string instead of a richer type. Depending on the domain logic, further manipulation might be required (for instance ensuring that the zip code matches the state, something that would be difficult/impossible to validate with regex).
– Jared Smith
yesterday
You could, but as soon as you do so, you are ascribing it some domain-specific behaviour, and that is exactly what the quoted articles are saying should lead to the creation of a custom type. The question is not whether you can do it in one style or the other, but whether you should, assuming your programming language gives you the choice.
– IMSoP
yesterday
You could, but as soon as you do so, you are ascribing it some domain-specific behaviour, and that is exactly what the quoted articles are saying should lead to the creation of a custom type. The question is not whether you can do it in one style or the other, but whether you should, assuming your programming language gives you the choice.
– IMSoP
yesterday
You could model such a thing as a
RegexValidatedString
, containing the string itself and the regex used to validate it. But unless every instance has a unique regex (which is possible but unlikely) this seems a bit silly and wasteful of memory (and possibly regex compilation time). So you either put the regex into a separate table and leave behind a lookup key in each instance to find it (which is arguably worse due to indirection) or you find some way to store it once for each common type of value sharing that rule -- eg. a static field on a domain type, or equivalent method, as IMSoP said.– Miral
16 hours ago
You could model such a thing as a
RegexValidatedString
, containing the string itself and the regex used to validate it. But unless every instance has a unique regex (which is possible but unlikely) this seems a bit silly and wasteful of memory (and possibly regex compilation time). So you either put the regex into a separate table and leave behind a lookup key in each instance to find it (which is arguably worse due to indirection) or you find some way to store it once for each common type of value sharing that rule -- eg. a static field on a domain type, or equivalent method, as IMSoP said.– Miral
16 hours ago
add a comment |
Thanks for contributing an answer to Software Engineering Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fsoftwareengineering.stackexchange.com%2fquestions%2f386042%2fis-avoid-the-yo-yo-problem-a-reason-to-allow-the-primitive-obsession%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
2
A nice read concerning adresses: mjt.me.uk/posts/falsehoods-programmers-believe-about-addresses Make sure you understand what you need and if a zipcode that is part of an address is really what you need and want to model.
– Polygnome
yesterday
1
"I should create a ZipCode object to represent zip code instead of a String object." ...Why? Zip codes would have no additional functionality to attach, and the most common thing you'll do with them is render them as part of a full address. Maybe you'll pass them to a geocoding service, but a custom object makes that harder. The Address class itself can validate (which shouldn't be more than just correct length and optional parts to avoid restricting valid inputs unknown to you at delivery time). I just don't see any practical advantage to having a zip code object; it's just boilerplate.
– jpmc26
19 hours ago
4
@jpmc26 Then you would be shocked to see how complex our zip code object is -- not saying it's right, but it does exist
– Jared Goguen
15 hours ago
3
@jpmc26, I fail to see how you get from "complex" to "badly-designed." Complex code is often the result of simple code coming into contact with the complexity of the real world rather than the ideal world we might wish existed. "Back to that two page function. Yes, I know, it’s just a simple function to display a window, but it has grown little hairs and stuff on it and nobody knows why. Well, I’ll tell you why: those are bug fixes."
– Kyralessa
9 hours ago
4
@jpmc26 - the point of wrapping objects like ZipCode is type safety. Zip code is not a string, it's a zip code. If a function expects a zip code, you should only be able to pass a zip code, not a string.
– Davor Ždralo
6 hours ago