

Replacing Throwing Exceptions with Notification in Validations - moks
http://martinfowler.com/articles/replaceThrowWithNotification.html

======
twic
> 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
The 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.

------
Animats
The 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?

~~~
dragonwriter
> 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.

------
jordanlev
What 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.)

------
dangayle
>>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?

------
yazaddaruvala
Tip 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");

------
striking
Interesting. 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)

------
pierotofy
User 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
Exactly. 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
> 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.

------
tel
This 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
This 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.

------
mjohn
This 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.

------
nitrogen
In 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.

------
jkaptur
I 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
"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
> 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
Exceptions 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.

