Paul Clapham wrote:But I'm just saying that the data structure you end up with isn't a tree.
Junilu Lacar wrote:
Paul Clapham wrote:but I certainly wouldn't call them "elegant".
One of my criteria of elegance is to be able to distill and separate complex and difficult to manage ideas into simpler and more manageable ideas in a clear and straightforward way. Separation of concerns helps get you there. The idea that Marriage is when Two become One is not just a romantic concept.
Junilu Lacar wrote:Well, it's kind of just what I suspected. None of these tests are very good in that they don't lay out a clear story of how People and FamilyTree are used from a non-"implementation aware" point of view.
Take this test, for example:
What does isLegitimate(any(), any()) mean, semantically? I know it's a mock object that you're calling this on, but then that implies that this is a whitebox test that "knows" something about what goes on when the addRelationship() method is invoked. To introduce a mock in this test is saying "I expect the FamilyTree to invoke the siblings.isLegitimate() method as a result of having its addRelationship() method invoked. But what do the "any()" parameters mean? To me that says that "I expect siblings.isLegitimate() to always return false regardless of what two parameters are passed to it." That doesn't make sense to me.
The story around the name shouldReturnFalseOnAddingIllegitimateRelationshipToFamilyTree isn't very obvious either. Why are you expecting the result to be false? I know that's what the test method name says but why? When I see "illegitimate" in the context of families and siblings, I think of someone born out of wedlock. Those are the natural semantics that come to mind when you use "illegitimate" in close proximity with "sibling" and "relationship". So what does false mean as a result of adding an illegitimate relationship to a family tree?
I could make many of the same kind of comments about the other tests. Bottom line is, they don't present a very good definition of a coherent and cohesive API that makes sense.
Junilu Lacar wrote:
What does isLegitimate(any(), any()) mean, semantically? I know it's a mock object that you're calling this on, but then that implies that this is a whitebox test that "knows" something about what goes on when the addRelationship() method is invoked. To introduce a mock in this test is saying "I expect the FamilyTree to invoke the siblings.isLegitimate() method as a result of having its addRelationship() method invoked. But what do the "any()" parameters mean? To me that says that "I expect siblings.isLegitimate() to always return false regardless of what two parameters are passed to it." That doesn't make sense to me.
The story around the name shouldReturnFalseOnAddingIllegitimateRelationshipToFamilyTree isn't very obvious either. Why are you expecting the result to be false? I know that's what the test method name says but why? When I see "illegitimate" in the context of families and siblings, I think of someone born out of wedlock. Those are the natural semantics that come to mind when you use "illegitimate" in close proximity with "sibling" and "relationship". So what does false mean as a result of adding an illegitimate relationship to a family tree?
I could make many of the same kind of comments about the other tests. Bottom line is, they don't present a very good definition of a coherent and cohesive API that makes sense.
Junilu Lacar wrote:Here's something that I think would make more sense:
Now, granted that this test is a bit long and the name sure could use some refactoring, doesn't it make more sense what the Person and FamilyTree objects are supposed to do even when you don't have a single clue as to how the methods that you see, setPatriarch(), addChild(), and isRelated() are doing what they apparently are able to do based on the story that the test is telling? Isn't the test code telling a coherent, understandable story?
NOTE: If you're unfamiliar with who most of these people are, you can probably figure out most of it if I tell you that Michael (the late, great, "King of Pop") Jackson's family is from Gary, Indiana. Bo Jackson was a well-known sports figure who was most popular in the 80s and early 90s. As far as I know, he's not related to the Jacksons of Gary, Indiana.
And it's easy enough to refactor that test name and identify a way to shorten up this test or maybe split it up into two separate tests.
These tests will define class behavior from an API perspective, not from an implementation perspective. I don't know if it's obvious what "isRelated should be commutative" and "isRelated should be transitive" means but if you see some code, I bet you'll understand it. Or maybe you do get what these mean and you can easily provide the test code that will exercise that property of relationships.
Here's what you need to keep in mind when you're doing TDD: Think API and semantics before you think about implementation.
A corollary to that is this: The danger of using mocks too early on when you're doing TDD is that mocks can lead to think about implementation before you have a coherent and well thought-out API.
Sangel Kapoor wrote:if you try to add Male as spouse of another male , it is illegitimate. I am not sure in what directions you are thinking, how does these tests are not conveying you the meaning. These are Unit Tests and not integration Tests. It just deals with Family Tree responsibility of saving and retrieving relationships.
In my humble opinion it is telling me the story .
Sangel Kapoor wrote:
1. There is no need to make Person Specifically, because family tree has nothing to take with person names. You should mock.
2. You are always adding relationship to family tree , no matter what the sex of person is and whether caller is calling in a right way or not. So you will addHusband even when person is female.
3. This API seems different in a way I am solving this problem. Probably design in your mind is different from mine.
Sangel Kapoor wrote:This is how API looks from Client
Sangel Kapoor wrote:
How can you represent this as a Tree is confusing me .
Binary or Not-binary does not matter at all.
In a Tree you have one Parent, and we have notion of Father and Mother both in FamilyTree we are taking about.
You might use forest for this, but still you cannot represent 2 parents.
I wrote:premature optimization and thinking about normalization can box your thinking into something that's not workable
Sangel Kapoor wrote:Also FamilyTree might not be a Tree DataStructure , in the same way as you said word "Controller" does not mean same Controller in Design Patterns (MVC).
Sangel Kapoor wrote:How can you represent this as a Tree is confusing me .
Binary or Not-binary does not matter at all.
In a Tree you have one Parent, and we have notion of Father and Mother both in FamilyTree we are taking about.
You might use forest for this, but still you cannot represent 2 parents.
Also FamilyTree might not be a Tree DataStructure...
"Leadership is nature's way of removing morons from the productive flow" - Dogbert
Articles by Winston can be found here
Winston Gutkowski wrote:
And then you get families like mine, where I have a half-brother and half-sister who aren't related.
Junilu Lacar wrote:Here's where I think you're falling short: thinking about usage scenarios.
You're thinking too deeply about classes, attributes, error conditions, etc. Again, I think it's too early for that level of thinking. You have to think at a higher, more abstract level.
Here's what I had in mind when I was writing those examples:
1. If I had a single family, how would I draw up their family tree? Let's take the Jackson family for example. Joe Jackson is the father and Tito, Jermaine, and Michael are his sons. Ok, let's see what kind of API would make sense to establish a family tree for these guys.
2. I'd like to see if someone is related to someone else. Ok, then if I had the Jackson family tree set up, then it should be able to tell me if it has information about Joe, Tito, Jermaine, and Michael being related to each other. Furthermore, if I had Bo Jackson, the family tree should be able to tell me that Bo is not related to the other Jacksons because Bo is not in that family tree.
This is the narrative that's reflected in those example tests I gave.
Draw up some high-level usage scenarios for using the API that you had in mind first. Then make some objective, critical evaluations, keeping in mind that most people don't come up with a good design with their first few tries. You saw that above when I started exploring alternative API designs right after coming up with that first one.
Even if you want to consider error conditions first, avoid mocks and don't think too much about implementation. Try to keep things small for now. By limiting the API to addChild(Person) instead of the wide open addRelationship(Person, Relationship, Person), I can focus on a simpler mental model rather than get confused with a more complex model that comes with the more generic API. With addChild(), I only have to think about and test the Parent-Child relationship. Once I have that established, I can make the small logical extension of considering sibling relationships based on a common Parent. If I then wanted to think about refining the Sibling relationship to Brother/Sister, then I can throw in Gender/Sex as part of the picture. The point is that I can grow the design incrementally and slowly add complexity from a solid base.
With addRelationship(), the complexity virtually explodes in my face right from the start. Why try to fly when you haven't even learned how to crawl yet?
Campbell Ritchie wrote:If you really are going to model families, you end up with two trees; I have a tree with descendants in, and I also have a tree of all my ancestors. Obviously the tree of descendants is smaller.
Sangel Kapoor wrote:Interface with abstract APIs first but still seems like you are not liking it
Junilu Lacar wrote:And before you try to explain more clearly what you intended, that's exactly the point I'm trying to make about your API and test code not being intuitive. If it were intuitive, you wouldn't have to explain it all and anyone reading the code who didn't have a psychic connection to your brain would know exactly what you were thinking.
Junilu Lacar wrote:
Just one more example, to help you understand why I don't feel this is a very good test or API.
On line 55 you invoke the addRelationship method, passing in a male, a relationship called 'siblings', and a female.
Then on line 56 you ask the family tree for a relationship that somehow involves the relationship, siblings and you call this list spousesAfterFirstAddition
If I were to read this out loud in an attempt to understand the "story" you're trying to tell with this code, it would be like this:
First, you establish a sibling relationship in the family tree between this male and female. So the male and female are brother and sister (because of the siblings relationship)
Then you ask the family tree to give back the list of people that are involved in the siblings relationship.
Why are you calling it spousesAfterFirstAddition? Are you expecting the brother and sister to be considered as spouses now? WTH?![]()
Next, on line 58, we're asserting that the list should only have one Person in it. What? Why? When you created the relationship, there were TWO people involved. How can we expect only ONE person to be returned now when we query for the same relationship?
Next, on line 59, we're asserting that the person in the list should be the female. Why the female? Why not the male? Why not both?
So, it takes a little bit of analysis to realize that you're thinking about "siblings" in terms of nodes in a tree and their relative positions. That is, your design of a husband and wife "couple" is that they will be two sibling nodes in the graph/tree. But in a Graph, the concept of "sibling" makes no sense. There's just "neighbor" on a Graph. A Tree has a logical hierarchy with parent-child relationships, with sibling nodes being child nodes that have the same parent node.
But you're mixing the implementation semantics of sibling with the semantics that are naturally associated with the abstract concept of human relationships in a family tree. When you say "sibling" in the abstract sense, that means brother or sister. In your code, "sibling" refers to the implementation semantics of tree nodes rather than the abstract semantics.
This is why your test code is confusing and it's muddying up your API as well as your design thinking around your API.
I wrote:
... This looks like procedural code rather than object-oriented code.
Dave Tolls wrote:Got to agree re: Relationship.
It's too prescriptive.
Beyond knowing X and Y are parents of Z pretty much every other relationship can be calculated.
Want to know how many siblings Z has? Then find out who the children of Y's parents are.
"Leadership is nature's way of removing morons from the productive flow" - Dogbert
Articles by Winston can be found here
I wish to win the lottery. I wish for a lovely piece of pie. And I wish for a tiny ad:
Smokeless wood heat with a rocket mass heater
https://woodheat.net
|