Lew said:
This is treading into the matter of art. I can show the idiom, but no
way can I generate an entire project for you to show the impact of the
logging strategy across the project, which is the point of the idiom.
Your entire application has layers. The deeper low-level layers are
most likely to encounter problems first. Runtime exceptions are
indicative of programming errors, but they're slippery, too. You don't
have to catch them, which means an unguarded runtime exception can
bring your system crashing down. That's not something you want to
design it to do.
A low-level layer by definition brings some model of the world into
conformance with your application's model. It might read a file and
extract data to objects, or persist objects to a database, or
transform your application's model into and out of XML documents for
communication to other modules. Whatever the low layer does, the
higher-level client code deals with abstractions relevant to the
program. It's no longer a database record, for example, it's a
'Customer' with related 'Order' elements in a collection.
If something goes wrong at a deep layer, the higher-level client must
first, become aware of it, and second, have some way to handle it. At
that level, "runtime exception" or 'IOException' are too low-level -
they aren't in the semantics of the application model. So the lower
level, in addition to transforming extrinsic models of good stuff into
the application domain, also must transform bad stuff like exceptions.
One good way to do this is to have an application-specific checked
exception like 'FooException' for the "Foo" application. As Patricia
says, this marks for higher-level code that lower-level stuff has done
its job. At the higher level, we care only that the details are
packaged in the exception's 'cause', not what the details are. A
'FooException', being checked, must be caught, so it cannot
accidentally crash the application. Since all higher-level code sees
'FooException' as a marker for bad stuff, and a black box, they all do
the same thing with it, more or less. They return the program to valid
state
"state" - definable by a quick Google search, for Pete's sake! Come
on, man! Do a /little/ of the lifting yourself. And no, this isn't
"bludgeoning". You get your ego in the way and you won't learn.
There's a reason we expect you to act like a professional.
such as a decision screen or restart position, perhaps extracting
standardized fields from the 'FooException' for the error message.
Much of programming only makes sense if you think architecturally -
that is, holistically. Each layer has its own job, and its own
vocabulary for that job. "Foo" has no business knowing about "I/O" in
its Foo-specific logic, and no business knowing about "Foo" behavior
in its I/O logic.
Separation of concerns.
Law of Demeter.
Object oriented.
Modularity.
I can see why it's hard to be specific about this issue without actually
building a whole, complex project. I don't want to ask too much of anyone
on this newsgroup and building a complex project is surely excessive.
It's just that I really need to hear both principles AND concrete
examples to be sure I really understand the principles. But let's try to
keep this at the principle level for the most part.
I see your point about how the low levels of a program are very narrowly
focused on doing something like finding a resource and don't know - and
shouldn't care - about the significance of that low level task. Those low
level methods don't know what the resource bundle is for or what the
consequences are if the bundle can't be found. It makes sense that the
decision about what to do if that search fails should be higher up in the
logic. The higher level will know if that is a show-stopper or is
recoverable. Some programs may have practical recovery options if the
bundle is missing while others may not.
I suppose it also makes sense that the higher level logic wants to think
in terms of "Foo problems" rather than IllegalArgumentExceptions. The
person writing Foo just wants to know if the problem in the lower level
method is a show-stopper or is recoverable and probably doesn't care if
it is a checked or unchecked exception or an error.
But I'm not quite seeing all the implications yet.
Are you envisioning that our hypothetical Foo would have a single
FooException that it wraps around everything that percolates up from the
lower levels? Or would there be a bunch of FooExceptions, each handling
different sorts of problems?
And what happens with logging? Do we just log the error as an
IllegalArgumentException (or whatever) when we find it at the low level?
Or do we also log it again, this time as a FooException, when we deal
with it at the higher levels? I'm uneasy about writing what are
effectively redundant messages to the log.
For what it's worth, I've cobbled together a CrashDialog class which my
higher level methods, getLocalizedText() for example, displays when it
handles an IllegalArgumentException() if it is thrown by getResources().
(I'm now throwing IllegalArgumentException for nulls in the parameters as
well as a failed getBundle()). In that case, the CrashDialog class says:
===================================================================
Foo - Severe Error
The program has to stop for the following reason: Certain language
resources were not available.
The problem has been logged and Technical Support will begin resolving
the error shortly.
[Okay]
====================================================================
The line about Technical Support resolving the problem shortly is
somewhat fanciful since I'm the whole development team and the entire
universe of end users at the moment ;-) I'm just envisioning what I'd say
in a real production application.
The bit about the language resources being unavailable is not completely
satisfactory because it is probably not very helpful to a non-technical
user but it seemed better to summarize the situation than to use the
message from the exception; the user is even less likely to comprehend
"the baseName must not be null" or "Unable to find resources for base
name, com.novice.foo.FooTextzzz, and locale, jp_JP. Check the spelling of
the base name". And I certainly don't want to display a stacktrace to the
user!
I've also logged the fact that the user clicked on okay to end the
program as another SEVERE error. Mind you, that seems redundant and I'm
inclined to remove that code again. I'm assuming that in the real world,
one SEVERE error is going to be enough to get the attention of the
appropriate people and producing two or three SEVERE errors about the
same problem is not going to get the problem examined any sooner. Correct
me if I'm wrong!
Now, that behavior wouldn't be standard for all cases. If the error is
recoverable, the user would get different instructions that would lead
him/her through whatever needs to be done to recover. And some errors
might not be severe at all and just be informational. In those cases, the
dialog might say "Information" instead of Severe Error and simply
describe whatever workaround Foo adopted to get past the problem.
I'm feeling good about the error handling and logging now. It's starting
to fall into place for me, aside from the whole custom exception
question.