On Tuesday, July 20, 2010 02:50:14 pm Jordan Ritter wrote:
> IME, the meanings of "safe vs. bang" methods and "what class of errors do
> exceptions represent" have always been, at their core, a conscious
> philosophical difference between DM and AR.

If it were possible to support both philosophies, wouldn't that be better?

> One (much older) school of thought: exceptions are "exceptional behaviour"
> -- they typically represent "unrecoverable" events.  Divide by zero is an
> example; failure to save an object to a DB because of validations is not;
> failure to save an object to a DB because of a driver failure (e.g. DO
> Mysql) is.

Those distinctions are, every last one of them, arbitrary and application-
dependent.

If I'm writing a calculator, divide-by-zero is at least as much a 
"recoverable" error as any other malformed user input.

On the other hand, if we're writing actual web _applications_, not just thin 
CRUD-to-HTTP layers, that validation error could be my internal application 
state that's getting silently corrupted.

> Error return values (bool, int, nil vs. not, etc) are for
> communicating recoverable failures.

I'm going to guess that this is the same school of though that grew up on C. 
The reason it's older is simply that exceptions didn't exist in C, and don't 
exist in all languages.

The problem is, if it's a recoverable failure, I'd still better recover from 
it. If I silently ignore it, it's now a silent error.

So let's see:

> It also provides for the "shortcircuit on error" code pattern,
> which in properly defensive code keeps the linear flow of code limited to
> success conditions.  All sorts of long-term benefits ensue from being able
> to match code comments/logic explanation to actual code implementation and
> flow.

Is that worth silently failing on a single mistake?

> One (more recent) school of thought: generally associated with "fail fast
> and hard" - everything is an exception. Exception meanings are expanded to
> mean "error of any kind", and it's up to the developer to figure out what
> to do with it, depending on where they rescue/catch it.  While it makes
> for convenient, DRY catch points (e.g. by controller) for common classes
> of exceptions, system/application state tends to be indeterminate because
> Exceptions jump out of stack frames and scopes (this is really, really
> important).

As opposed to it being indeterminate because I thought I saved something, and 
I didn't.

> Something that could be recoverable, isn't, unless you
> rescue/catch immediately.

This is by definition true.

Let me put this another way: Say I get an ECC error. What do you think I 
should do? Should I try to handle it gracefully? Should I bring the system 
down hard? Or should I ignore it and hope it doesn't corrupt anything else?

Amazon S3 was down for 7-8 hours because a single bit flipped:

http://status.aws.amazon.com/s3-20080720.html

Given that kind of failure, I have to say, failing that single system 
immediately and loudly would've been far better than watching the entire 
datacenter go down.

> And even then, depending on the type of
> Exception, still maybe isn't.   Maybe.

I don't see how this is different than what was proposed before.

> Outside of the development process,

One moment before we step outside... Can we agree, in general, that we want 
our production systems to function more or less like our development systems?

For example, if it's a good debugging tool to enable raise_on_save_failure, 
and if I were to leave it on during development, I would have to do a fair 
amount of additional testing once I'd switched to production mode.

> Production-quality, hardened core
> systems are not allowed to fail unless absolutely necessary.

And that means coding defensively and recovering from errors where you can, 
yes. But all that is, really, is a difference in syntax. Your example is 
'unless object.save' or 'if object.save ... else ... end', versus 
begin/rescue/end. It's still the same process to prevent the system from 
failing.

However, if an error happens that the programmer didn't see, well, see the 
Amazon story above. No one knows whether that bit was flipped because of some 
obscure programmer error or because of RAM. It's quite possible there's 
nothing that would've raised an exception. But in a situation like that, an 
unhandled exception is FAR less harmful than a silently-ignored error.

Yes, in a production system. If you're getting tons of unhandled exceptions, 
you're not production-ready. If you're getting tons of silently-ignored 
errors, you're also not production-ready, you're just less likely to know 
about it.

> It's flat out wrong to assert DM encourages lazy behaviour: it merely
> advocates a different school of thought.

The default DM API makes the simple, natural, easy thing to do, wrong.

That is bad design. It's not just a case of syntactic vinegar, or laziness. 
It's a case of, how else would you suggest I ensure this never happens:

u = User.new
u.name = 'John Smith'
u.save
puts 'Saved successfully! (I hope.)'

How else do I ensure that in every case I tried to save something, I made sure 
it saved properly?

> But the fact that
> you have raise_on_error in DM means you get your choice of either school,
> so I guess I don't understand your point.

I think it's wrong and dangerous, and I'd like people to stop doing it, even 
if I could just "choose my school" and go my way.

But what I actually want to accomplish here is to make good on that promised 
choice. The current implementation of raise_on_error doesn't give me that. 
>From my last message:

> On Jul 19, 2010, at 8:46 PM, David Masover wrote:
> > So yes, it makes library code mildly nicer... I don't think that's a
> > good trade, especially considering that it actually makes things worse,
> > if people were to ever actually use raise_on_save_failure. Now, instead
> > of my library code looking like this:
> > 
> > begin
> >  current_object.save
> >  ...
> > rescue whatever
> >  ...
> > end
> > 
> > It'd look like this:
> > 
> > old_rosf = current_object.raise_on_save_failure
> > if current_object.save
> >  save_successful!
> > else
> >  save_failed!
> > end
> > current_object.raise_on_save_failure = old_rosf
> > 
> > Wait, that's the naive, non-threadsafe way to do it. Guess I have no
> > choice but to do it like this:
> > 
> > if current_object.raise_on_save_failure
> >  begin
> >    current_object.save
> >    save_successful!
> >  rescue whatever
> >    save_failed!
> >  end
> > elsif current_object.save
> >  save_successful!
> > else
> >  save_failed!
> > end

It occurs to me that I'm pretty sure the above has a race condition if 
anything changes raise_on_save_failure on the fly, so even this isn't a 
perfect solution.

And I do occasionally write library code. I have actually run into this 
specific issue while writing library code.

So tell me: Short of the above, how am I supposed to write any sort of plugin, 
especially something like a Rails Engine, which talks to DataMapper, unless I 
simply assume users always have raise_on_save_failure turned off?

The real way to fix this would be to create separate methods for each 
operation, one which throws exceptions, and one which doesn't. This is 
complicated by the fact that DM already uses the bang operators to save 
without validations.

If it were up to me, and I were allowed to break existing apps, I'd do 
something like this:

def try_save
  return false unless valid?
  save_without_validations
end

def save
  try_save || raise 'save failed!'...
end

def save_without_validations
  # actually persist to the datastore, return false on failure
end

But it's not up to me, and I'm a relative newcomer, so instead, I do my 
"store" hack, because changing the semantics of "save" would be a fairly huge 
change. Still, I'd like to have something like this in core -- maybe both 
try_save and save_or_raise, in addition to the current save?

-- 
You received this message because you are subscribed to the Google Groups 
"DataMapper" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/datamapper?hl=en.

Reply via email to