Re: try, finally

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Thanks for the long response, Ted. Although I disagree with most of
it, I appreciate your time. Some of the points you have made are
slightly unclear:

On Wed, Mar 19, 2008 at 5:04 PM, Ted Byers <r.ted.byers@xxxxxxxxxx> wrote:
>  The following code strikes me as an abuse of exception
>  handling.  Exceptions ought to be reserved for
>  unpredictable, rare error conditions.

How would you handle predictable, common error conditions, then? And
what do you consider to be unpredictable and rare? Going back to the
XML example (since it's an easy one to build on), let's say I have a
document structured like this:

<root>
  <something>
    <value>
      <data>
      </data>
    </value>
  </something>
</root>

If I was writing code to parse that, and found "root", then
"something", then "value", but not "data", I would call that a rare
error. Also the only way to really recover from it is to let the user
know what happened, and to leave the program in a valid state or
terminate it. Using exceptions that way provides for both in a very
simple way. In my examples, for simplicity, I threw a Something() --
but associating error messages with exceptions, and a good exception
inheritance structure, makes error handling and reporting very easy
(e.g. throw EDocumentFormatError(__FILE__, __LINE__, "Data node is
missing from file.")). Take Java as an extreme example of the use of
exceptions.

> Unless you are
>  doing ONLY batch processing, you want your interface
>  to be as user friendly as possible.  Therefore, you
>  want to, and can, know precisely what conditions would
>  result in your call to, say, xmlReadFile failing, and
>  can test for it.  If your test(s) for such conditions
>  that could lead to failure identifies a potential
>  problem, you can notify your user of the problem and
>  prompt him to take corrective action.

By this I assume you mean ask libxml for a clear error string and
somehow display it to the user, correct? If that is the case, I agree.
I hate receiving error messages such as "Error: Operation failed", and
so I do what I can to not display vague messages like that to the user
either. I did leave all that out of my example. However, including the
error message in an exception is an extremely clean way of passing
that information back to the caller. What would your alternative be?
Returning error codes from functions becomes messy, and setting global
error strings with functions such as GetLastErrorMessage() (or
whatever, you get the idea) comes with its own set of problems.

> Your error
>  condition, then, doesn't arise.

I am not sure what you mean. If a file does not exist and therefore
can not be opened, notifying the user of the error does not cause the
file to exist.

> User error WRT data
>  input is common enough that it is much better to test
>  for all errors that you can test for, and prompt the
>  user for corrective action than it is to just throw
>  exceptions willy nilly every time a statement doesn't
>  give you what you wanted.

Agreed. I don't see what is willy nilly. In this particular example, I
have an XML file that contains some data that I need to load. The XML
file must adhere to a very specific structure that has been defined
elsewhere. When the user chooses to load the file, the file must
either be 100% compliant with the defined structure, or it will fail
to load. I have no desire to prompt the user with message boxes such
as "Sorry, but the X data element was missing from this data file,
would you mind telling me what you think the value should be so I can
recover and continue?" A user would not be able to answer that
question in my case anyway. Instead, if the file has a slight problem
(it should not, as the files are not hand-generated), I want to notify
the user that the file could not be loaded. Of course, I would provide
them with a reason why the file could not be loaded, but it is not
always necessary, or even possible, to allow the user to intervene and
correct an error condition themselves. Therefore, in the XML example,
if any one of those operations fails, the entire load operation
*should* fail, and therefore every operation warrants an error return
on failure. In this case, exceptions are a great error return, because
they can store a lot more information than, say, returning an integer
from a function. In fact, this is *precisely* what exceptions are
designed for.

>  To use a trivial example, suppose your code does
>  mathematical analysis and you routinely evaluate a
>  quotient "x / (a - b)".  You KNOW going in that you'll
>  encounter a divide by zero error condition either if a
>  = b or both 'a' and 'b' = 0.  Instead of just letting
>  the error happen, you test for the possibility of the
>  error condition, and when detected you terminate the
>  affected call, ...

I agree completely. This is very reasonable.

> ...returning control to a function that
>  prompts the user for corrective action and handle the
>  result.

This seems like a reasonable way to do that:

class EDivideByZero : public Exception { ... };

int DoTheMath (int x, int a, int b)
  throw (EDivideByZero)
{
  if (a == b)
    throw EDivideByZero("Divide by zero: A must not equal B");
  else
    return x / (a - b);
}

void DoWhatever () {
  try {
    DoTheMath(4, 2, 4);
  } catch (Exception &e) {
    DisplayTheError(e.TheMessage);
  }
}

This notifies the user of the problem, and this also allows
DoWhatever() to handle errors without ever having to have knowledge of
what the error was. It is up to the source of the error to describe
the error.

>  If a programmer working for me showed me code like
>  that which you show here, I would probably send him
>  back to the drawing board for there is no evidence of
>  any analysis of error conditions and possible error
>  prevention.

I am not sure what you mean. There is analysis of error conditions --
all operations are checked for errors. Errors are not left unhandled.
If by no "error prevention" you mean that the user is not given any
notification of *what* operation caused the error -- well I assure you
that's because I left out specific error messages in my example, since
I was asking about SEH and they weren't relevant.

>  Such a failure guarantees a less than
>  adequate user experience, and significant wasted user
>  time, squandered on trying to figure out why the
>  program didn't do what the user expected it to do.

Much of what you say is based on the fact that my example had no
specific error messages. Given that, everything you say is completely
reasonable. However, in the actual code, I do provide error messages,
so most of the issues you are talking about are not problems. In fact,
I even include the file name in the error messages. <g>

>  > If my goal is to reduce the amount of coding I have
>  > to do, it is far
>  > easier for me to duplicate the cleanup code than to
>  > go and write a
>  > bunch of small C++ wrapper objects with automatic
>  > cleanup that I can
>  > use interchangeably with the libxml2 data types.
>  >
>  This doesn't make sense.  If you can write cleanup
>  code, regardless of which library you're using, you
>  can do so efficiently using RAII.  You can't use the
>  existance of one badly written library or another as a
>  rational for poorly structuring your own code.

I am not attempting to rationalize poor structuring of any code.

>  If for
>  some reason, you don't like existing wrapper libraries
>  for libxml2, then roll your own.  Trust me, your
>  preference for copy and paste duplication of your
>  "cleanup" code will come back to bite you at a most
>  inconvenient moment.  Your code will bloat as you
>  develop it further, eventually becoming a maintenance
>  nightmare.

Yes, I know this. This is precisely why I came here to ask about SEH
in GCC... SEH is a feature in other compilers that I use precisely to
prevent the bloat. GCC did not provide it, and now I am looking for an
alternative. You are preaching to the choir, my friend.

> Trust me, I have worked on projects that,
>  efficiently coded, had in excess of half a million
>  lines of code, and when a project gets that big, the
>  last thing you want to do is refactor to remove code
>  duplication.  It is much better to get it right the
>  first time through that to have to fix it after it has
>  become a bloated monster.

Again you are preaching to the choir. I promise. :-)

>  Yes, both do.  std::auto_ptr and boost::shared_ptr,
>  and there are variants of each used for pointers to
>  arrays.  You do have to make sure you use the right
>  version of each, for each pointer you're replacing.
>  But if you have to use functions like 'xmlFree' to
>  properly clean up, there is no option other than to
>  write your own class that invokes such functions in
>  its destructor.

Thanks. Actually, it seems that the boost::shared_ptr lets me use
xmlFree without writing my own class to do it -- see me22's previous
reply -- which is a perfect solution to my issue of duplicating
cleanup code.

> The smart pointers only invoke delete
>  on the enclosed pointer at the right time.

They can do other things besides delete.

> Even so,
>  writing a wrapper class for a resource you have to
>  manage remains trivially easy, and much more
>  maintainable than your copy and paste approach.

Writing a wrapper class is not necessary as long as your cleanup
function takes only one parameter (such as xmlFree), see
boost::shared_ptr. However, it is definitely more maintainable than my
copy and paste approach.

> And
>  if you must keep your resource on the heap rather than
>  on the stack, you would still use your custom resource
>  class WITH one of the smart pointers.

You can just use the smart pointers directly.

>
>  HTH

It is certainly food for thought. I do appreciate your reply.

>
>  Ted

Jason

[Index of Archives]     [Linux C Programming]     [Linux Kernel]     [eCos]     [Fedora Development]     [Fedora Announce]     [Autoconf]     [The DWARVES Debugging Tools]     [Yosemite Campsites]     [Yosemite News]     [Linux GCC]

  Powered by Linux