Hi Elder,
But here's another perspective: going back to the FileNotFoundException
example, with checked exception, the default is that I have to handle
FileNotFoundException.
This is where I disagree. Instead, I think that we have to distinguish two
cases:
1) You know the file exists when you try to open it. There may be several
reasons why the file should always exist. For example, it could be a vital
file that gets installed with your application, or it might be a temporary
file that the application has just created, or it might be a file that the
user just selected in an "open file" dialog, or you may have tested that the
file exists before calling Open, etc.
2) You don't know whether the file exists or not at the point where you try
to open it. For example, you application may use a "load path" to locate
some of its files, and the file may only be in one of the directories along
the load path, or it may even be completely absent, in which case the
application will use some defaults instead or reading from the file (this is
just a typical scenario).
I think that the right way to program the file open logic in these two cases
is the following:
1) Just call File.OpenText, don't catch the FileNotFoundException locally.
Instead, let it bubble up, it will be caught by the generic catch handler.
2) Test if the file exists with File.Exists(filename) before calling
File.OpenText.
Why do it this way?
In case 1), the event that the file does not exist is really an exceptional
event, and, 99% of the time, there is nothing you can do locally to handle
this exceptional event. So, the best thing to do is to let it bubble up like
any other exceptional event (bug, other I/O exception) naturally to the
framework catchall handler (event loop, event dispatcher) where it will be
logged and reported. Of course, this assumes that the framework always calls
your code after setting up a proper try/catchall. If it does not, you should
set up one in the entry points of your code (your main, your event
handlers -- for me it is crucial that all exceptions get caught and reported
somewhere higher in the call chain).
In case 2), you could catch FileNotFoundException instead of testing with
File.Exists. But the advantage of testing with File.Exists is that it tells
the reader that you are not dealing with an exceptional case, but with a
condition that you anticipated and that may occur in a "normal" context.
Also, by writing it this way, you clearly separate the logic that deals with
"normal" cases from the logic that deals with "exceptional" cases (try/catch
is only used for exceptional cases)
With this approach, you reduce drastically the number of exception handling
constructs (try/catch) in your code. Basically, all you need is a few
catchall clauses in strategic places in your framework, where you will log
and report all exceptional events in a uniform way, and all your "normal"
logic is expressed with normal control flow statements (if/then/else, while,
foreach, etc.).
Then, you can easily review the "normal" logic on one side, and the
"exception handling" logic on the other side. And check that exceptions are
not intercepted and ignored as some level (which is very bad because
something went wrong and nobody will ever know about it).
If at that time I decide that in the context of
code I'm writing I'm not supposed to handle it, I'll just rethrow it (or
don't catch it at all), and let some other code up there who knows how
to handle it, handle it. So I can be "sure" that I'm letting it through
because I don't know what to do with it, in my current context, and that
somebody up there who knows MUST handle it, either by crashing, or by
opening up a dialog to let the user enter the file location.
Ok. But then, what's the real advantage of catching locally and rethrowing,
instead of letting the exception bubble up naturally.
I can see only one advantage: you can wrap the low level exception into a
higher level exception that gives a more understandable error message to the
user (I use this try/catch/rethrow pattern quite often).
On the other hand, now pretend that I'm writing a piece of code that
knows how to handle FileNotFoundException. Without checked
exception--because I don't have to do anything, the compiler doesn't
even point out to me that I am supposed to do something about it, it is
more likely for me to forget that there are cases that I AM supposed to
handle, and it won't make sense for me to rethrow them to my superior.
Someone who codes higher up there may assume that I have handled it,
because now the situation is reversed, it doesn't make sense for HIM to
handle it. As such, he doesn't handle it, and he's not prepared for it,
so the application crashes. Which may not be how it is supposed to be.
Well, everytime you call some File.Open method, you know that you have to
ask yourself: what if the file does not exist? And then, you have two
answers: either this would be an exceptional event (case 1 above), or this
is a normal event that your logic should test (case 2 above). In the second
case, I think that an if (File.Exists(filename)) test is better than
catching FileNotFoundException, for the reasons stated above.
From a more theoretical standpoint, I think that checked exceptions give the
impression that the compiler will be able to check whether your logic has
holes or not. This is a very attractive idea (the more errors the compile
can check, the better) but it is a only a mirage because the compiler will
report "false positive", i.e. things that it believes are holes in your
logic but that in fact aren't.
Every time you are calling File.Open in a context where you know that the
file exists (or after having checked its existence with File.Exists), your
logic does not have any hole, and the compiler is simply wrong in telling
you that you have to catch FileNotFoundException.
So, I think that the fundamental problem with checked exceptions is that it
gives the impression that the compiler will be able to do some useful
"semantic" checks on the code, but these checks are not so useful because
they are polluted by "false positives". The fundamental problem is with a
piece of code like:
if (File.Exists(filename))
TextReader rd = File.OpenText(filename);
Here, with checked exceptions, the compiler would force you to catch
FileNotFoundException, or to list it in your throws clause, simply because
the compiler is dumb and only does a very shallow analysis of the semantics
of your code. If it knew as much as you do, it would be able to figure out
that this piece of code will never throw FileNotFoundException, and it would
not force you to catch this exception that will never happen (except in real
"exceptional" situations where a separate process would delete the file
between the File.Exists test and the OpenText call, of course).
So with checked exception, I'm always reminded of things that I may need
to handle, and it is in my discretion to decide which one to let go.
Without checked exception, nobody tells me what to handle, it is in my
discretion to decide which one NOT to let go. This is what gives me this
guilt I haven't been able to get rid of
I understand, but it forces you to deal with lots of "false positives" too!
That is, taking a probably far-fetched analogy: even without
documentation, checked exception is like having a option dialog for a
GUI application: you see all the options available to you, so you can
see which one you want to change.
But without checked exception, it's like a unix command line app--you
don't even know what options are available to you, you have to sift
through the man page to look for them, and you can always miss one or
two, because it's not so right-in-your-face.
Well, why would you ask for detailed error messages, tracebacks, etc when
you have "access denied" and "core dump"? You are really too demanding
Bruno.