-
-
Notifications
You must be signed in to change notification settings - Fork 546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bowling: be more explicit about when errors should happen #536
Conversation
I think it is desirable to fail as soon as possible, as in general that is a good strategy to follow. How to specify it, I'm not sure. That's a bit harder I think. |
OK, nobody gave a better suggestion, so I'm rolling with |
"If the `expected` key is present and non-negative", | ||
"all rolls should succeed, and the final score should equal that value.", | ||
"", | ||
"If the `expected` key is present and negative,", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, now using the error object of #401. |
The JSON file currently says "When to error is also left up to your implementation", but we should instead be explicit. Let's say I `roll 5`, then `roll 6`, then `roll 0` 18 times. Okay, that's 20 rolls, that's a full game, so I can take the score now, right? Now I try to take the score and find that I get an error. Argh! In the absolute best case, the error result from score would be so kind to tell me that my `roll 6` way back there was invalid, but in the worst case it might not, and I would be left to my own devices to figure out what happened, since there was no feedback on any of the individual rolls. This commit proposes that invalid rolls be made explicit right when the roll is made, not at some unspecified time in the future (when attempting to take the score of the game). This is done via a `roll_should_fail` key that is mutually exclusive with the `expected` key. Previous discussions I know of (since I was involved): * exercism/rust#213 (comment) * exercism/haskell#440
Just a comment, that shouldn't prevent merging this: The fact that you are proposing to use a |
I think that's because there are two methods being tested here:
With the PR almost being merged, we could be using the |
So you are suggesting that the tests look something like the below. I gave one example for each of the three different cases represented:
[ { "description" : "game with no strikes or spares"
, "property" : "score"
, "previous_rolls": [3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6]
, "expected" : 90
}
, { "description" : "can't score incomplete game"
, "property" : "score"
, "previous_rolls": [0, 0]
, "expected" : {"error": "something"}
}
, { "description" : "can't roll more pins than there are on the lane"
, "property" : "roll"
, "previous_rolls": [5]
, "roll" : 6
, "expected" : {"error": "something"}
}
] |
Actually, shouldn't the last case be: { "description" : "can't roll more pins than there are on the lane"
, "property" : "roll"
, "previous_rolls": [5]
, "roll" : 6
, "expected" : {"error": "something"}
} With the property being tested set to |
You're right. Edited to be so, so that we are on the same page |
I think your three test-cases example looks perfect now! Very clean and understandable. Great! |
First of all, let me say that I agree with the idea of returning which of the rolls in the lists in invalid. It is an useful information for the user. Sorry for getting in the middle of this PR, I was just thinking out too loud. Now, let me explain what I meant.
This may be where we diverge. I think the exercise is just about scoring a game, so we need to test To fulfill the new requirements of returning which roll is the invalid, this
A valid score should be represented normally in "expected": 42 An exceptional condition would, by convention, be represented as an "expected": { "error": "Incomplete game" } or "expected": { "error": "Invalid roll 6 - Cannot score more than 10" } I was just thinking that, because Anyway, it seems that this PR has taken a turn, and now the proposal includes also testing an intermediary function. In this case, I think that my original arguments doesn't apply anymore. |
@rbasso I'd also be content with your solution, but I do really like to "fail-fast" in my code, and failing on an invalid roll instead of only failing when calculating a score seemed reasonable to me. |
First, I will say that I will propose a new file to match the new schema, which should make clear when the errors are happening. But not now. Maybe in 12 hours. Second, I will say that the Haskell track only has a Now I see what we mean when we say we want a richer error than just |
About when to fail
Think in what I wrote more like a question than a suggestion. By the way, I also prefer to fail-fast most of the times, @ErikSchierboom. I was just pondering that maybe we are letting details of "how to solve the problem" affect the "how to specify the problem". If the solution gives the correct results, it shouldn't matter how it does it internally. About more structured expected types
I just started thinking about the subject after we added the restrictions from #551 in #602, @petertseng, but maybe the problem can be generalized a little. Maybe Let's say we have a function called
We have a lot of different ways to encode that in JSON: [ { "description": "Try to feed electronics"
, "property" : "feedShark"
, "food" : "laptop"
, "expected" : false
}
, { "description": "Try to feed a big animal"
, "property" : "feedShark"
, "food" : "elephant"
, "expected" : "Impossible"
}
, { "description": "Try to feed vegetables"
, "property" : "feedShark"
, "food" : "lettuce"
, "expected" : { "feederEaten": "right arm" }
}
] We could also be more descriptive about the cases, like this: [ { "description": "Try to feed electronics"
, "property" : "feedShark"
, "food" : "laptop"
, "expected" : { "eaten": false }
}
, { "description": "Try to feed a big animal"
, "property" : "feedShark"
, "food" : "elephant"
, "expected" : { "impossible": {} }
}
, { "description": "Try to feed vegetables"
, "property" : "feedShark"
, "food" : "lettuce"
, "expected" : { "feederEaten": "right arm" }
}
] We could even flatten everything, maybe for readability: [ { "description": "Try to feed electronics"
, "property" : "feedShark"
, "food" : "laptop"
, "eaten" : false
}
, { "description": "Try to feed a big animal"
, "property" : "feedShark"
, "food" : "elephant"
, "impossible" : {}
}
, { "description": "Try to feed vegetables"
, "property" : "feedShark"
, "food" : "lettuce"
, "feederEaten": "right arm"
}
] Edit: Fixed the example so that they make sense! |
`git diff -w` on this commit should be mostly clean. In preparation for new schema.
This makes clearer that the rolls are assumed to happen prior to the test at hand.
The transformation is simple: Any case with a "roll_should_fail" turns into a "property": "roll" case with an appropriate error. All other cases turn into a "property": "score" case.
The unindent is inevitable to match the new schema. But that's why it's in its own commit, so that it is easily seen that that commit only unindents (and removes one level of nesting by removing |
IMHO, the current version is great. What do you think @rbasso? Perhaps we should move the discussion about return values to a separate issue? |
I'm usually against testing for intermediary functions, but I never solved this exercise and I trust that you have your reasons. 👍
It was just an idea...I guess I'm spending too much time with JSON. 😄 |
@rbasso Well, the problem description requirements actually explicitly mention two operations to be implemented:
:) |
You are indisputably right about that. 😄 |
Sorry, no squash since I think the individual changes are easier to understand than a single diff that purports to change the entire file (it really doesn't) I was a bit selfish and only served my specific goals for this PR: The goal of expressing in some way that a roll should fail fast (as soon as it is known to be erroneous). There are other goals that are still worthy, even though I left them aside for this PR. I kept the scope small for this PR, but these are worth their own discussion: More structured data in
|
🎉 |
The JSON file currently says "When to error is also left up to your
implementation", but we should instead be explicit.
Let's say I
roll 5
, thenroll 6
, thenroll 0
18 times.Okay, that's 20 rolls, that's a full game, so I can take the score now,
right?
Now I try to take the score and find that I get an error. Argh!
In the absolute best case, the error result from score would be so kind
to tell me that my
roll 6
way back there was invalid, but in the worstcase it might not, and I would be left to my own devices to figure out
what happened, since there was no feedback on any of the individual
rolls.
This commit proposes that invalid rolls be made explicit right when the
roll is made, not at some unspecified time in the future (when
attempting to take the score of the game). This is done via a
roll_should_fail
key that is mutually exclusive with theexpected
key.
Previous discussions I know of (since I was involved):