7 ms·
Replacing Throwing Exceptions with Notification in Validations
- nitrogen 12y agoIn Ruby, I prefer exceptions for most errors because they make the error handling more visible and explicitly defined, but if performance is an issue, returning a value (or setting an error on an ActiveRecord model) is much faster than raising an exception.
- deleted 12y ago[deleted]
- pierotofy 12y agoUser input validation benefits from using notifications, internal logic checks (input coming from another part of the code) should raise exceptions. Expected behavior --> notifications. Unexpected behavior --> exceptions. Simple.
- mwhite 12y agoExactly. This article doesn't address the fact that for most programs you ideally want to have a separate UI validation where you're dynamically validating the user input before it gets submitted, and an internal validation where you validate the data before it gets saved, but also possibly even after it gets saved, relative to pre-existing data (where you could throw an exception and abort the transaction if it didn't validate). If you want to use some of the same validation code for user input validation and internal validation, I suppose you could modify the notation pattern to conditionally throw an exception when adding a new error depending on whether it's on the client or the server.
- dragonwriter 12y ago> If you want to use some of the same validation code for user input validation and internal validation, I suppose you could modify the notation pattern to conditionally throw an exception when adding a new error depending on whether it's on the client or the server. Or you reuse the validation code itself, but called from a wrapper that turns notifications into exceptions, rather than adding branches.
- jordanlev 12y agoWhat a strange coincidence -- I was working on this exact same problem with a small validation library for a project, when I took a break to read Hacker News. I feel so smart right now because Martin Fowler validated (no pun intended!) my own thinking on this issue. Both conceptually (a form validation error is not exceptional -- it is expected behavior that you want to handle within the flow of your program), and practically (throwing exceptions makes it harder to display multiple errors for the same field, so the user winds up playing "whack-a-mole" when there are a lot of different rules... e.g. "password must be at least 6 characters", "password must contain a letter and a number", etc.)
- striking 12y agoInteresting. In my validator [1], I take an entirely different approach. Everything throws an exception... which is caught and rethrown at every level, which you can then "interrogate." I did it this way because if someone calls the validation function and wants the method to fail immediately if something is wrong, it will. No need to check, it just does. But if you'd like to try validating and see if something messes up, you can! [1]: https://github.com/callmeStriking/affirm-json https://github.com/callmeStriking/affirm-json (see src/validator.js or, heck, just run lib/valitest.js)
- jkaptur 12y agoI like this pattern a lot. On a recent project, I used it almost exactly, but with a richer model of 'error'. Even though it was just basic form validation, I thought it led to very clean code.
- dragonwriter 12y ago"Unexpected" is an ill-defined term; you've clearly anticipated, on some level, an event occurring if you have written code to throw an exception. One criterion you could apply is that exceptions should only be thrown for violations of the code's published expectations that can not be statically prevented. Whether a validation failure would be "unexpected" then depends on whether the code is expressly validation code that expects unvalidated data (in which case validation failures would not be unanticipated) or processing code which "expects" good data and does something with good data, but throws an exception on bad data -- note that in the same workflow, at different levels, both models could be used, with different levels creating or swallowing exceptions depending on the expectations of the particular function.
- blueflow 12y ago> if a failure is expected behavior, then you shouldn't be using exceptions Just from the abstract view - isn't 'exception' defined as unexpected event? Or am i missing something?
- Retra 12y agoExceptions are not for unexpected behavior, but for state that is inconsistent with your model. Unexpected behavior is generally called an "error" or a bug. You can't code around the unexpected.
- Animats 12y agoThe advantage of exceptions is that if the caller ignores the error, it gets detected anyway. The Go crowd argues that code should handle errors as normal events, but that leads to code with this repeated over and over: result, err := fn1() if err != nil { return err } Or worse, the return is replaced with a "goto". As for the original article, JSON is machine to machine communication. Why do you need to report more than the first error? One of the programs talking is broken. What next, parser error recovery for bad JSON?
- justincormack 12y agoJSON is becoming less like that, the JSON might encode a form the user has filled in, and it might be reporting the set of validation errors, as part of the API.
- dragonwriter 12y ago> JSON is machine to machine communication. Why do you need to report more than the first error? One of the programs talking is broken. What next, parser error recovery for bad JSON? JSON is a data serialization format; it might be used for machine-generated data, but it can also be used to serialize raw user input and transport it to another node for validation. For the latter use case, multiple errors can be important.
- yazaddaruvala 12y agoTip for the author: Validations that only contain human readable text is a nicer pattern to follow than their exception counter parts, but its not complete. You will find yourself wanting to know, programmatically, which JSON key that error message was for. note.addError("numberOfSeats", "number of seats must be positive");
- mjohn 12y agoThis came up recently on a Python project at work. I think the approach in the article is nice but most Python libraries seem to make use of exceptions for these kinds of scenarios. Working against that trend in Python doesn't seem worth it. In other languages I would probably prefer to follow the approach in the article.
- dangayle 12y ago>>if a failure is expected behavior, then you shouldn't be using exceptions How would this pattern translate to Python, where exceptions are the norm and expected behavior?
- twic 12y ago> Firstly I'm not happy with using exceptions for something like this. Exceptions signal something outside the expected bounds of behavior of the code in question. I've been hearing this line since the '90s. I've never heard a justification for it, or a rigorous explanation of what "expected" means. Exceptions are a language construct, like variables, classes, and for loops. As with those other constructs, use them when it results in a program which makes sense. But that's a judgment you can only make on the program, not on the construct.
- jerf 12y agoThe following will sound cynical, but I'm serious. The "justification" is nothing more and nothing less that the original creators of the exception intended it to be used that way. It's the same reason people still quote Alan Kay as if his opinions about "object orientation" apply to everything that happens to wear that label 40 years later, and as if his opinion is some sort of controlling vote over whether something is "truly" OO or worse, good OO. While I am all about respecting our predecessors and their good work (probably more so than most HN folk, though my respect rarely shows on HN itself; much of it is on topics not HN-related), I dislike that sort of fealty to their opinions. It shuts down thought and conversation. Whether exceptions should be "exceptional" should be something discussed carefully, not simply true by definition. Personally I'd observe that due to immense variances in exception semantics and performance in all the various runtimes and languages of the world there isn't a blanket statement you can make about whether that is true; for instance, C++ and Python have radically different criteria for what an "exception" should be, written into their nature, and just dogmatically repeating "exceptions should be exceptional" won't help you understand anything about the details of either language, or how they differ.
- dragonwriter 12y ago> I've never heard a justification for it, The usual justification I've heard is that it is superior for the flow of operation for expected cases to be clear from inspecting the code, and the use of exceptions breaks that up. > or a rigorous explanation of what "expected" means. This is a problem. I have my own construction of what it makes sense for "expected" to mean in this context [0], but its not clear to me that that's what others offering this rule necessarily do mean by "expected", it usually seems to be fuzzier (which is problematic.) > Exceptions are a language construct, like variables, classes, and for loops. As with those other constructs, use them when it results in a program which makes sense. But that's a judgment you can only make on the program, not on the construct. I generally agree, but I think that -- with the understanding of "expected" relating to the advertised constraints/expectations of the unit of code in question -- its a fairly good rule of thumb for when using exceptions results in code that makes sense. [0] https://news.ycombinator.com/item?id=8746874 https://news.ycombinator.com/item?id=8746874
- tel 12y agoThis pattern is totally embedded into the non-monadic applicative Either sometimes called Validation data Validation e a = Collect e | Value a deriving Functor instance Monoid e => Applicative (Validation e) where pure a = Value a Collect le <*> Collect re = Collect (le <> re) Collect e <*> _ = Collect e _ <*> Collect e = Collect e Value f <*> Value a = Value (f a) There is no monad instance for this which lets us scoot forward through a computation and and all of the errors at once.
- HolyHaddock 12y agoThis is worth understanding, and works out a lot better in practice than having to pass in ad-hoc collection builders as equally ignorable "out-params" or exceptions.