http://www.perlmonks.org?node_id=231243


in reply to Re: Best Practices for Exception Handling
in thread Best Practices for Exception Handling

The fact is that when you write code in modular fashion one part of your system cannot always know how to handle errors itself. In such cases the only thing you can do is pass error somewhere else and there are in general two ways to do it: exceptions and return codes. And exceptions is just a more robust way to do it.

Example: say you are implementing business logic for your application which has multiple frontends (CLI, web and GUI). This part of your application encounters an error (let say a database connection error). What should it do? Print HTML page with error? Produce plain text formated error message for CLI? Write something in the log? No, it is not responsiblity of this part of your system to do these things, it is responsiblity of the frontend part to handle this error. So you just raise an exception and let the frontend to handle it.

--
Ilya Martynov, ilya@iponweb.net
CTO IPonWEB (UK) Ltd
Quality Perl Programming and Unix Support UK managed @ offshore prices - http://www.iponweb.net
Personal website - http://martynov.org

Replies are listed 'Best First'.
Re: Best Practices for Exception Handling
by jonadab (Parson) on Feb 03, 2003 at 14:35 UTC
    Example: say you are implementing business logic for your application which has multiple frontends (CLI, web and GUI). This part of your application encounters an error (let say a database connection error). What should it do? Print HTML page with error? Produce plain text formated error message for CLI?

    You said this was modular code, right? Right?

    Doesn't sound like it to me. If it was, you'd just call the routine that produces an error message, and let that routine use its Big Switch Statement to decide how exactly to do that. (If the routine that prints an error message encounters an error while doing so, you'd probably consider that fatal and die.)

    This sounds pretty similar to the exception-throwing way of doing things if all you're doing is printing an error message, but the more common case would be where you have to actually do something, such as ask the user for better input. In that case, you're calling a routine that asks the user for input, and it's using a switch statement to decide which routine to call -- the one that puts up a dialog box, the one that prints the question on STDOUT and gets the answer on STDIN, the one that prints up a web form and waits for the form processing script to signal it with an answer (identifying this instance by matching a hidden field token in the form against a list of such tokens and corresponding PSIDs), or the one that posts the question to usenet and checks periodically for a response. Whichever routine you call, it will have its own ideas about how to make as many attempts as necessary until it gets valid data, at which point it will return that answer to the caller.

    I'm starting to think maybe we're doing the same thing in opposite ways. You're reducing complexity by moving all knowledge of what's going on into the caller, and I'm reducing complexity by leaving the caller with knowledge only of what it wants to do with the data and moving all knowlege of where the data comes from into the subroutine. It may be a different paradigm.

    In some ways, your approach reminds me very much of my brief experiments with the event-oriented paradigm when I took courses in two "fifth-generation" languages in college. As you probably know, event-orientation turns everything around by making user input the caller and the program's logic the callee. I have a bad taste in my mouth for this approach, possibly because the only languages I've used that do things that way are VB and Lingo, both of which I loathe, especially Lingo. Though now that I think of it, it might be possible to do something like it in Perl, sort of, and that might not be so bad. [ponders this]

    I'm not sure how to fall off the end of the main code block (which presumably would initialise the objects and stuff) while leaving the various objects in place, however. It might be necessary to use some kind of big loop (which I suppose is probably what VB and Lingo do under the hood anyway). Come to think of it, the Inform standard library works something like that; I never thought of it as event-oriented, but now that I think about it, it really is. I'm sure I could do something like that in Perl (though I could never write anything as unbelievably complex as the Inform standard library; I'm convinced Graham Nelson is a genius).

    Am I completely out in left field here, or am I beginning to understand?

     --jonadab

      Do you agree that the logic responsible for asking users for a better input belongs to the user interface part and it is not a part of the business logic? I.e. business logic layer have to callback user interface part when it tries to do the error recovery. So you still have to pass control to the callee to do the error recovery that business logic part cannot do on its own as if you were using exception or return codes style of error handling. To me it looks that the error recovery mechanism is essantially the same in both cases.

      The only difference is added complexity of callback approach. Let's plot a couple of diagrams. Traditional approach (exceptions or return codes) for case when the business logic part bails with error:

      UserInterface BusinessLogic | user input | |------------------------------>| | do some action | |<----------failure-------------| | handle the error | |
      If at the last point the user interface part can handle the error it can either ask the business logic part to redo the action or if this error is unrecoverable print diagnostic error or do something else.

      Now callback approach:

      UserInterface BusinessLogic UserInte +rface | user input | |------------------------------>| | do some action | |----------failure------------->| | handle th +e error | |

      This diagram clearly show two problems:

      1. This design adds additional requirement on reentrability for at least the user interface and probably for other parts of the system if the error recovery callback calls them.
      2. If error recovery callback can handle the error then program flow is clear. It returns the control to the business logic part which in its turn returns the control to the callee, i.e. back to the user interface part. But what about another case when it cannot handle the error? You still have to return the control back using either exceptions or return codes! Why then bother with callbacks at all?

      --
      Ilya Martynov, ilya@iponweb.net
      CTO IPonWEB (UK) Ltd
      Quality Perl Programming and Unix Support UK managed @ offshore prices - http://www.iponweb.net
      Personal website - http://martynov.org

      Doesn't sound like it to me. If it was, you'd just call the routine that produces an error message, and let that routine use its Big Switch Statement to decide how exactly to do that. (If the routine that prints an error message encounters an error while doing so, you'd probably consider that fatal and die.)

      I think this part of the confusion might be due to the phrase "the routine that produces an error message". One of the advantages that exceptions give you is that there isn't a error handler, but multiple error handlers.

      We don't want our low-level abstraction to have to know about the various ways its being used. We don't want our high-level abstractions to have to know about each other. If we have a single error handler then it has to know about every possible use. Every time we add a new way of handling the error we have to add it to our Big Switch Statement. Having to have one piece of code that knows about every possible usage is producing needlessly tight coupling between modules.

      With an exception handling style error conditions and error handlers are separated. There is no "Big Switch Statement". Each error handler is local to the module that needs to handle an error condition in a particular way.

      You can happily add new modules that handle the error in a different way without having to alter any other piece of code.

      I'm starting to think maybe we're doing the same thing in opposite ways. You're reducing complexity by moving all knowledge of what's going on into the caller, and I'm reducing complexity by leaving the caller with knowledge only of what it wants to do with the data and moving all knowlege of where the data comes from into the subroutine. It may be a different paradigm.

      I would characterise the difference as where you choose to handle the error.

      • You're proposing to handle the error where it occurs. This allows direct access to the "context" the error occured in. However the high level context (GUI vs CLI or whatever) has to be made available to the error handler in some way.
      • In exception handling you handle the error in the code that knows how to handle the error. This allows direct access to the high level context. However, the low-level context has to be passed to the error handler in some way.

      Passing the high-level context down to the error handler is can be tricky. You've got globals, a Big Switch Statement, or you have to pass the calling context from the highest level down to the lowest level in some way (adding a special error handling object to every call for example). All of which (in my opinion) make the code harder to maintain - especially if you have a system with many layers and many possible error conditions and handlers.

      There are, of course, some situations where you have to handle the error in the context it occurs in to be able to do anything useful. Some languages provide direct support for doing this sort of error handling cleanly (e.g. handler-bind in Common Lisp), but they provide exception handling too!

      That said, in my experience, having to handle the error in the calling context of the routine that caused the error is rarely necessary.

      Passing the low-level context up to an error handler is simple - it goes into the exception object when the error occurs.

      In some ways, your approach reminds me very much of my brief experiments with the event-oriented paradigm when I took courses in two "fifth-generation" languages in college. As you probably know, event-orientation turns everything around by making user input the caller and the program's logic the callee. I have a bad taste in my mouth for this approach, possibly because the only languages I've used that do things that way are VB and Lingo, both of which I loathe, especially Lingo. Though now that I think of it, it might be possible to do something like it in Perl, sort of, and that might not be so bad. ponders this

      Don't throw away event-handling because of VB & Lingo :-) It's a very useful coding style, and pretty much essential for many kinds of problem. Take a look at POE for one perl approach.