Re: Execption Handling disection
>> Well, I have been using this methodology on a fairly large project (6[color=blue][color=green]
>> years, more than 10 developpers involved, multi-tier application,
>> multi-threaded kernel, probably over a million lines of code at this
>> stage) and it does scale up![/color][/color]
You making this up Bruno? You guys hiring :-)
--
Regards,
Alvin Bruney - ASP.NET MVP
[Shameless Author Plug]
The Microsoft Office Web Components Black Book with .NET
Now available @ www.lulu.com/owc
"David Levine" <noSpam12dlevin eNNTP2@wi.rr.co m> wrote in message
news:esAk8F0QFH A.2972@TK2MSFTN GP14.phx.gbl...[color=blue][color=green]
> >
>> Well, I have been using this methodology on a fairly large project (6
>> years, more than 10 developpers involved, multi-tier application,
>> multi-threaded kernel, probably over a million lines of code at this
>> stage) and it does scale up![/color]
>
> Well, I must admit that is a good argument. Does it require a lot of
> developer discipline, or does it tend to be self-regulating?
>[color=green]
>>
>> It does not mean doubling every API, there are actually just a few calls
>> that need to be doubled, mostly calls that parse strings, lookup things
>> by name, open files, load resources by name, etc. This is a very small
>> fraction of the APIs, and the overhead of doubling these calls is really
>> not a problem, especially if you have a good naming convention (we use
>> Find for the methods that throw and Lookup for the methods that return
>> null). The rest of the API only comes in one flavor.[/color]
>
> What other conventions have you adopted to support this?
>[color=green]
>>
>> Also, you don't always need to duplicate the entry points. Sometimes, it
>> is better to pass an extra parameter to indicate whether errors should be
>> signaled through exception or whether they should be returned through
>> some kind of error object. For example, most of our parsing routines take
>> an "errors" argument. If you pass null, the parser will throw exceptions
>> and will always return a valid parse tree. If you pass an errors object,
>> the parser will collect the errors into it, and will return null if the
>> parsing fails. This is a typical example of clever API design, that gives
>> us the two flavors in one call without adding much complexity to the API
>> (I did not invent it, there are plenty of examples in the LISP APIs of
>> emacs).[/color]
>
> I've seen other APIs that take a "throwOnErr or" argument but I am not fond
> of it (yet). I prefer a single path throw the code, not two. Have you ever
> encountered problems related to this?
>[color=green][color=darkred]
>>> It also does not address the problems developers face with 3rd party
>>> libraries that do not supply the either/or API - a wrapper for each API
>>> would need to be written that wrapped the one that threw the exception
>>> and returned a sentinel value (or vice-versa), otherwise the try-catch
>>> flow control code goes back into the main body of code.[/color]
>>
>> Yes, this is a problem, and we setup such wrappers for calls that are
>> used in many places in our code (fortunately, this does not happen very
>> often).
>>[/color]
>[color=green]
>>
>> In 95% of the cases, there is not much you can do "locally" about the
>> special case/exception (your functional analysis should tell you that).
>> So, the right thing to do is to call the "non-Try" version and let the
>> exception bubble up. In the remaining 5%, you know that you have to deal
>> with a special case (your functional analysis should tell you that) and
>> you call the "Try" version.[/color]
>
> What kind of functional analysis are you referring to? Perhaps you analyze
> things a bit differently then what I am accustomed to.
>[color=green][color=darkred]
>>>
>>> It is not necessary to log at each try-catch handler. I recommend
>>> logging at the initial catch site and again (if necessary) if the
>>> exception is about to leave the module boundary.[/color]
>>
>> No, we only log when we don't rethrow, this way you know that every
>> exception will be logged and logged only once.[/color]
>
>
> I tend to disagree but perhaps for practical reasons that probably don't
> apply to most situations today. When we first transitioned from C/C++
> Win32 to managed code no one really knew what best practices to apply...it
> evolved. As a result the original code base was littered with empty
> try-catches and exceptions were getting swallowed, converted, etc. all
> over. My reaction to that was to establish requirements to never allow an
> exception to get silently dropped again. The result was double-logging -
> the first time when it was initially thrown and the last time when it was
> handled or left the module boundary - this way if it got dropped somewhere
> in the middle we would be able to detect it.
>
> The 2nd practical result was that I made it a requirement that all
> swallowed exceptions must call a central method (called SwallowExceptio n)
> that by default printed out the exception message to the Trace - one of
> the arguments to the method is the reason why it was ok to swallow the
> exception. As a result we found a lot of places in the code that needed
> work to either remove the source of the exception or do some other
> rewrite. There are circumstances when swallowing an exception makes sense,
> and most of those fall into the category that we are discussing - when to
> throw versus return some other value. IOW, wrapping the API into a call
> that does not throw would accomplish the same thing, and I'll probably
> switch over to using that mechanism - it makes sense.
>
>
>[color=green]
>> Notes: in both patterns, we catch "all exceptions". So, we are always
>> violating the rule that says that you should only catch "specific"
>> exceptions (this is one of FxCop's rule). This rule is stupid because it
>> is an encouragement to use exceptions as flow control in application
>> logic. If you don't use exception as flow control for special cases that
>> should be tested by your application logic, they should all bubble up the
>> same way (get wrapped with higher level message, and then logged).
>>[/color]
>
> Agreed. It is a silly rule.
>
>[color=green]
>> There is nevertheless one case where we log and rethrow, this is when we
>> design an API that someone else will be using, and when we know that this
>> someone else is not very rigorous about logging exceptions. In this case,
>> we do our own logging in the entry points of our component and we rethrow
>> the exception (so that the someone else still gets an exception). But
>> this is just so that we don't loose the information if the client of our
>> component does not follow the rules (does not log every exception that he
>> gets from our component).
>>[/color]
>
> That sounds like the same sort of rule I use, which is to log when an
> exception leaves the boundaries of the module.
>
>
>[/color]
>> Well, I have been using this methodology on a fairly large project (6[color=blue][color=green]
>> years, more than 10 developpers involved, multi-tier application,
>> multi-threaded kernel, probably over a million lines of code at this
>> stage) and it does scale up![/color][/color]
You making this up Bruno? You guys hiring :-)
--
Regards,
Alvin Bruney - ASP.NET MVP
[Shameless Author Plug]
The Microsoft Office Web Components Black Book with .NET
Now available @ www.lulu.com/owc
"David Levine" <noSpam12dlevin eNNTP2@wi.rr.co m> wrote in message
news:esAk8F0QFH A.2972@TK2MSFTN GP14.phx.gbl...[color=blue][color=green]
> >
>> Well, I have been using this methodology on a fairly large project (6
>> years, more than 10 developpers involved, multi-tier application,
>> multi-threaded kernel, probably over a million lines of code at this
>> stage) and it does scale up![/color]
>
> Well, I must admit that is a good argument. Does it require a lot of
> developer discipline, or does it tend to be self-regulating?
>[color=green]
>>
>> It does not mean doubling every API, there are actually just a few calls
>> that need to be doubled, mostly calls that parse strings, lookup things
>> by name, open files, load resources by name, etc. This is a very small
>> fraction of the APIs, and the overhead of doubling these calls is really
>> not a problem, especially if you have a good naming convention (we use
>> Find for the methods that throw and Lookup for the methods that return
>> null). The rest of the API only comes in one flavor.[/color]
>
> What other conventions have you adopted to support this?
>[color=green]
>>
>> Also, you don't always need to duplicate the entry points. Sometimes, it
>> is better to pass an extra parameter to indicate whether errors should be
>> signaled through exception or whether they should be returned through
>> some kind of error object. For example, most of our parsing routines take
>> an "errors" argument. If you pass null, the parser will throw exceptions
>> and will always return a valid parse tree. If you pass an errors object,
>> the parser will collect the errors into it, and will return null if the
>> parsing fails. This is a typical example of clever API design, that gives
>> us the two flavors in one call without adding much complexity to the API
>> (I did not invent it, there are plenty of examples in the LISP APIs of
>> emacs).[/color]
>
> I've seen other APIs that take a "throwOnErr or" argument but I am not fond
> of it (yet). I prefer a single path throw the code, not two. Have you ever
> encountered problems related to this?
>[color=green][color=darkred]
>>> It also does not address the problems developers face with 3rd party
>>> libraries that do not supply the either/or API - a wrapper for each API
>>> would need to be written that wrapped the one that threw the exception
>>> and returned a sentinel value (or vice-versa), otherwise the try-catch
>>> flow control code goes back into the main body of code.[/color]
>>
>> Yes, this is a problem, and we setup such wrappers for calls that are
>> used in many places in our code (fortunately, this does not happen very
>> often).
>>[/color]
>[color=green]
>>
>> In 95% of the cases, there is not much you can do "locally" about the
>> special case/exception (your functional analysis should tell you that).
>> So, the right thing to do is to call the "non-Try" version and let the
>> exception bubble up. In the remaining 5%, you know that you have to deal
>> with a special case (your functional analysis should tell you that) and
>> you call the "Try" version.[/color]
>
> What kind of functional analysis are you referring to? Perhaps you analyze
> things a bit differently then what I am accustomed to.
>[color=green][color=darkred]
>>>
>>> It is not necessary to log at each try-catch handler. I recommend
>>> logging at the initial catch site and again (if necessary) if the
>>> exception is about to leave the module boundary.[/color]
>>
>> No, we only log when we don't rethrow, this way you know that every
>> exception will be logged and logged only once.[/color]
>
>
> I tend to disagree but perhaps for practical reasons that probably don't
> apply to most situations today. When we first transitioned from C/C++
> Win32 to managed code no one really knew what best practices to apply...it
> evolved. As a result the original code base was littered with empty
> try-catches and exceptions were getting swallowed, converted, etc. all
> over. My reaction to that was to establish requirements to never allow an
> exception to get silently dropped again. The result was double-logging -
> the first time when it was initially thrown and the last time when it was
> handled or left the module boundary - this way if it got dropped somewhere
> in the middle we would be able to detect it.
>
> The 2nd practical result was that I made it a requirement that all
> swallowed exceptions must call a central method (called SwallowExceptio n)
> that by default printed out the exception message to the Trace - one of
> the arguments to the method is the reason why it was ok to swallow the
> exception. As a result we found a lot of places in the code that needed
> work to either remove the source of the exception or do some other
> rewrite. There are circumstances when swallowing an exception makes sense,
> and most of those fall into the category that we are discussing - when to
> throw versus return some other value. IOW, wrapping the API into a call
> that does not throw would accomplish the same thing, and I'll probably
> switch over to using that mechanism - it makes sense.
>
>
>[color=green]
>> Notes: in both patterns, we catch "all exceptions". So, we are always
>> violating the rule that says that you should only catch "specific"
>> exceptions (this is one of FxCop's rule). This rule is stupid because it
>> is an encouragement to use exceptions as flow control in application
>> logic. If you don't use exception as flow control for special cases that
>> should be tested by your application logic, they should all bubble up the
>> same way (get wrapped with higher level message, and then logged).
>>[/color]
>
> Agreed. It is a silly rule.
>
>[color=green]
>> There is nevertheless one case where we log and rethrow, this is when we
>> design an API that someone else will be using, and when we know that this
>> someone else is not very rigorous about logging exceptions. In this case,
>> we do our own logging in the entry points of our component and we rethrow
>> the exception (so that the someone else still gets an exception). But
>> this is just so that we don't loose the information if the client of our
>> component does not follow the rules (does not log every exception that he
>> gets from our component).
>>[/color]
>
> That sounds like the same sort of rule I use, which is to log when an
> exception leaves the boundaries of the module.
>
>
>[/color]
Comment