Timeline for answer to Avoiding throw because we are not sure the exceptions will always be caught by Mehdi Charife
Current License: CC BY-SA 4.0
Post Revisions
20 events
| when toggle format | what | by | license | comment | |
|---|---|---|---|---|---|
| Apr 22, 2024 at 13:29 | comment | added | Dreamer |
This solution works for some cases, but not for the majority of real-life cases. One more realistic example would be a Triangle class for geometrically valid triangles, i.e. those with non-zero area. Validity can easily be tested, but there's no straight-forward way to ensure arbitrary parameter values always result in a valid triangle.
|
|
| Apr 22, 2024 at 9:07 | history | edited | Mehdi Charife | CC BY-SA 4.0 |
deleted 1 character in body
|
| Apr 21, 2024 at 22:29 | comment | added | Ewan | @MaciejStachowski the OPs code does the same thing in a more complicated way. How its achieved isnt really the point, the point is you dont have to have this type of error at all | |
| Apr 21, 2024 at 20:52 | history | edited | Mehdi Charife | CC BY-SA 4.0 |
deleted 51 characters in body
|
| Apr 21, 2024 at 20:46 | comment | added | Mehdi Charife |
"The calling code probably wouldn't rely on catching the exception - it should probably provide its own validation closer to the frontend before calling your constructor. Or it can swap the dates itself" What frontend? Not all the objects that live in your program get created as a result of some "frontend" request. Some objects are created in order to collaborate in solving an algorithmic problem. This being said, it is also true that the additional complexity in Foo will also be incomparable to the one resulting from each caller having to do the validation or the swapping by iitself
|
|
| Apr 21, 2024 at 20:44 | comment | added | Mehdi Charife |
@MaciejStachowski "your code already has four methods, and you're doing the min()/max() dance in each of them. What if you forget to do that in method #37? " Additional methods should be able to rely on getStartDate and getEndDate to get all the information they need about the two dates, so there is no need to redo the min/max dance in each subsequent method. In fact, the getDuration and equals methods could also make use of the two getters. That said, needing to have 37 methods in one class might be an indication that the class probably needs some refactoring.
|
|
| Apr 21, 2024 at 17:22 | comment | added | Maciej Stachowski | @Ewan that's what one of the suggestions in the comments does, but that's not what the code in the OP does. The code in the OP needs to account for not knowing which date is the smaller one in every method. And I'm not sure why "a constructor that can't fail" (even if that were possible) would be a point of pride - constructors should fail when given invalid initialization parameters one way or another, not provide an invalid object. | |
| Apr 21, 2024 at 12:07 | comment | added | Ewan | @MaciejStachowski I think you have an incorrect impression here. If you define the class such that instead of passing in min and max dates, you just pass two dates and the smallest one is assigned to the min, then your not "over stuffing" you class with logic, youve just provided a constructor that cant fail. | |
| Apr 21, 2024 at 12:03 | comment | added | Ewan | jeeze 7 people think its better to throw an exception than write code so that the exception cause has a defined behaviour instead? | |
| Apr 20, 2024 at 11:24 | comment | added | Maciej Stachowski |
2) Is it? Is it really? Are you 100% sure? @DarrelHoffman's suggestion can sort-of ensure this since you reduce it to a known case as soon as possible, but your code already has four methods, and you're doing the min()/max() dance in each of them. What if you forget to do that in method #37? How certain can you be that all tests diligently test every bit of logic for both cases? How do you guarantee that invariant across the entire class?
|
|
| Apr 20, 2024 at 11:19 | comment | added | Maciej Stachowski | 1) The calling code probably wouldn't rely on catching the exception - it should probably provide its own validation closer to the frontend before calling your constructor. Or it can swap the dates itself if by some chance it is handling two arbitrary dates and suddenly needs a date range out of them. Overstuffing your code with logic that belongs upstream isn't usually a good idea - you could as well have your constructor accept strings (so that the caller doesn't have to parse dates) or show an error popup (so that the caller doesn't have to render it), but that way madness lies. | |
| Apr 20, 2024 at 11:11 | comment | added | Mehdi Charife | @DarrelHoffman Yes, I haven't though of that. Seems way simpler that way. | |
| Apr 20, 2024 at 11:09 | comment | added | Mehdi Charife |
@MaciejStachowski The additional complexity in the class seems incomparable to the one resulting from the client code needing to handle/throw the exception whenever it needs to instantiate your class. As to the point regarding the bug that doesn't happen when date1 <= date2 but happens when date2 > date1, this kind of scenario shouldn't be possible since the class behaviour is invariant under the transformation (x,y) -> (y,x), so if a bug happens when date2 > date1, it should also happen when date2 < date1.
|
|
| Apr 19, 2024 at 20:03 | comment | added | Schwern |
Let's say the caller makes a mistake and feeds garbage dates to Foo. In the first case, StartDateBiggerThanEndDateException is raised allowing the error to be found. In the second case, a valid Foo object is created, the caller's error is hidden, and the program continues careening along with invalid data probably creating more invalid data until something important is corrupted. This pattern is it is guessing at the caller's intent and cannot tell the difference between the caller intentionally swapping the dates and covering up an error in the caller.
|
|
| Apr 19, 2024 at 19:35 | comment | added | gnasher729 | Ewan, throwing in a constructor is tricky. You’ll have to consult the C++ standard to do it safely and it is always tricky. I don’t like tricky things. | |
| Apr 19, 2024 at 18:34 | comment | added | Darrel Hoffman |
Would it not be simpler to have the constructor simply swap the dates if they're in the wrong order? e.g. this.startDate = DateUtils.min(date1, date2); this.endDate = DateUtils.max(date1, date2); Then you don't have to override Equals() or provide those getStartDate(), and getEndDate() functions, since every object will have the dates in the correct order.
|
|
| Apr 19, 2024 at 18:26 | comment | added | Maciej Stachowski |
It might not be a good idea to introduce additional complexity to your class for the sake of not throwing or otherwise rejecting behaviour that isn't really needed. Let's say 99% of people are going to use this class with date1 <= date2, and you have a bug that only occurs when date2 > date1 - how long until such a bug gets caught when it's likely that even maintainers of Foo are going to forget you can provide the dates backwards? What if such a bug is an exploitable vulnerability?
|
|
| Apr 19, 2024 at 16:18 | comment | added | Ewan | Yeah I think these other answers have missed the point, "Don't throw in constructors" isn't really avoided by returning null or throwing in a factory. You have just dodged the rule. | |
| Apr 19, 2024 at 14:09 | history | edited | Mehdi Charife | CC BY-SA 4.0 |
added 10 characters in body
|
| Apr 19, 2024 at 13:44 | history | answered | Mehdi Charife | CC BY-SA 4.0 |