October 23, 2008
Steven Schveighoffer wrote:
> "Andrei Alexandrescu" wrote
>> Sean Kelly wrote:
>>> Andrei Alexandrescu wrote:
>>>> Robert Fraser wrote:
>>>>> Option B:
>>>>> ---------
>>>>> try
>>>>> {
>>>>>     new Socket(30587);
>>>>> }
>>>>> catch(Exception e)
>>>>> {
>>>>>     if(e.type == ExceptionType.Socket)
>>>>>         printf("Could not open socket\n");
>>>>>     else
>>>>>         throw e;
>>>>> }
>>>> I think you'd be hard-pressed to justify the "if" inside the second example. You couldn't create a Socket, period. It doesn't matter where exactly the exception was generated from.
>>>>
>>>> That's one thing about large exception hierarchies: everybody can come with cute examples on how they could be useful. As soon as the rubber hits the road, however, differentiating exceptions by type becomes useless.
>>> It may be different in a user application, but in services it's fairly common to have specialized code for handling different exception types. And more importantly, it's common to want different exception types to propagate to different levels for handling.  Sure, one could use a generic exception handler at each level that rethrows if the detected type isn't one that handler cares about but why do this when filtering on type is a language feature?
>>>
>>> For example, let's say I have a network service that's backed by a SQL database.  My main program loop may look something like this:
>>>
>>>     bool connected = false;
>>>     while( true )
>>>     {
>>>         try
>>>         {
>>>             while( true )
>>>             {
>>>                 auto r = acceptRequest();
>>>                 scope(failure) r.tellFailed();
>>>                 if( !connected )
>>>                     connectToDB();
>>>                 handleRequest( r );
>>>             }
>>>         }
>>>         catch( SqlException e )
>>>         {
>>>             connected = false;
>>>             log( e );
>>>         }
>>>         catch( Exception e )
>>>         {
>>>             log( e );
>>>         }
>>>     }
>>>
>>>     ...
>>>
>>>     void handleRequest( Request r )
>>>     {
>>>         scope(failure) r.tellFailed( "General error" );
>>>
>>>         try
>>>         {
>>>             // process r
>>>         }
>>>         catch( AuthException e )
>>>         {
>>>             r.tellFailed( "Authentication failure" );
>>>         }
>>>         catch( ValidationException e )
>>>         {
>>>             r.tellFailed( "Invalid request format" );
>>>         }
>>>     }
>>>
>>> Being able to trap specific types of exceptions makes this code cleaner and more succinct than it would be otherwise.  If this weren't possible I'd have to trap, check, and rethrow certain exceptions at different levels to ensure that the proper handler saw them.
>> Thanks for fueling my argument. There's duplication in code examples, as in many other examples I've seen in favor of by-type handling.
>>
>> First example:
>>
>>         catch( Exception e )
>>         {
>>             if (e.origin = "sql") connected = false;
>>             log( e );
>>         }
>>
>> Less code and no duplication. Second example is even starker:
>>
>>         catch( AuthException e )
>>         {
>>             r.tellFailed( e.toString );
>>         }
>>
>> Clearly the need is to factor in the message to print in the exception, at least in this case and many like it.
> 
> For the second example, you are assuming that ValidationException is a subclass of AuthException.

Sorry, I meant to catch Exception. My point was, if I want to print an informative message, the exception should be able to provide it without having to encode it in its type.

> I think in this case, ValidationException and AuthException might share a common parent (SqlException) which you do NOT want to catch.  It is advantageous in this case to have both a hierarchy through inheritance and a hierarchy through parameters.
>
> Here's another idea, to avoid rethrows, what about some pre-handler code that tests if an exception is what you want?  Perhaps the same mechanism that is used by template constraints:
> 
> catch(SqlException e) if (e.reason is Auth || e.reason is Validation)
> {
>    r.tellFailed(e.toString());
> }

I think we're a bit too immersed in the "what can we add to the language". In this case, for example, I see no reason for the "if" to be moved inside and call it a day.


Andrei
October 23, 2008
On Fri, Oct 24, 2008 at 4:08 AM, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> wrote:
> Andrei Alexandrescu wrote:
>>
>> Sean Kelly wrote:
>>>
>>> Andrei Alexandrescu wrote:
>>>>
>>>> Robert Fraser wrote:
>>>>>
>>>>> Option B:
>>>>> ---------
>>>>> try
>>>>> {
>>>>>    new Socket(30587);
>>>>> }
>>>>> catch(Exception e)
>>>>> {
>>>>>    if(e.type == ExceptionType.Socket)
>>>>>        printf("Could not open socket\n");
>>>>>    else
>>>>>        throw e;
>>>>> }
>>>>
>>>> I think you'd be hard-pressed to justify the "if" inside the second example. You couldn't create a Socket, period. It doesn't matter where exactly the exception was generated from.
>>>>
>>>> That's one thing about large exception hierarchies: everybody can come with cute examples on how they could be useful. As soon as the rubber hits the road, however, differentiating exceptions by type becomes useless.
>>>
>>> It may be different in a user application, but in services it's fairly common to have specialized code for handling different exception types.  And more importantly, it's common to want different exception types to propagate to different levels for handling.  Sure, one could use a generic exception handler at each level that rethrows if the detected type isn't one that handler cares about but why do this when filtering on type is a language feature?
>>>
>>> For example, let's say I have a network service that's backed by a SQL database.  My main program loop may look something like this:
>>>
>>>    bool connected = false;
>>>    while( true )
>>>    {
>>>        try
>>>        {
>>>            while( true )
>>>            {
>>>                auto r = acceptRequest();
>>>                scope(failure) r.tellFailed();
>>>                if( !connected )
>>>                    connectToDB();
>>>                handleRequest( r );
>>>            }
>>>        }
>>>        catch( SqlException e )
>>>        {
>>>            connected = false;
>>>            log( e );
>>>        }
>>>        catch( Exception e )
>>>        {
>>>            log( e );
>>>        }
>>>    }
>>>
>>>    ...
>>>
>>>    void handleRequest( Request r )
>>>    {
>>>        scope(failure) r.tellFailed( "General error" );
>>>
>>>        try
>>>        {
>>>            // process r
>>>        }
>>>        catch( AuthException e )
>>>        {
>>>            r.tellFailed( "Authentication failure" );
>>>        }
>>>        catch( ValidationException e )
>>>        {
>>>            r.tellFailed( "Invalid request format" );
>>>        }
>>>    }
>>>
>>> Being able to trap specific types of exceptions makes this code cleaner and more succinct than it would be otherwise.  If this weren't possible I'd have to trap, check, and rethrow certain exceptions at different levels to ensure that the proper handler saw them.
>>
>> Thanks for fueling my argument. There's duplication in code examples, as in many other examples I've seen in favor of by-type handling.
>>
>> First example:
>>
>>        catch( Exception e )
>>        {
>>            if (e.origin = "sql") connected = false;
>>            log( e );
>>        }
>>
>> Less code and no duplication. Second example is even starker:
>>
>>        catch( AuthException e )
>>        {
>>            r.tellFailed( e.toString );
>>        }
>>
>> Clearly the need is to factor in the message to print in the exception, at least in this case and many like it.
>>
>>
>> Andrei
>
> By the way, this each-exception-has-its-type crap is my #2 pet peeve after "Follow carefully my pocket watch... OO is good for everything... OO is good for everything... to use, inherit... to use, inherit..."

Interesting point.  The whole catch(A){} catch(B){} catch(C){} thing
is basically if-then-else on very similar types, which is exactly what
the OO crowd says you should try to avoid in any other situation.  You
should be using polymorphism!

But ok, types are good for one thing, and that's ensuring global uniqueness.  That seems to be the real point of the billion exception types, to give them unique identifiers that the compiler can check. You can give your exceptions a typecode or a string identifier, like your origin=="sql", but then the compiler won't tell you if your type is unique.  And it won't tell you there's a problem if you mistakenly type origin=="SQL" instead of "sql".

I think it boils down to a kind of static typing vs dynamic typing argument.

--bb
October 23, 2008
Bill Baxter wrote:
> On Fri, Oct 24, 2008 at 4:08 AM, Andrei Alexandrescu
>> By the way, this each-exception-has-its-type crap is my #2 pet peeve after
>> "Follow carefully my pocket watch... OO is good for everything... OO is good
>> for everything... to use, inherit... to use, inherit..."
> 
> Interesting point.  The whole catch(A){} catch(B){} catch(C){} thing
> is basically if-then-else on very similar types, which is exactly what
> the OO crowd says you should try to avoid in any other situation.  You
> should be using polymorphism!

Yes!

> But ok, types are good for one thing, and that's ensuring global
> uniqueness.  That seems to be the real point of the billion exception
> types, to give them unique identifiers that the compiler can check.
> You can give your exceptions a typecode or a string identifier, like
> your origin=="sql", but then the compiler won't tell you if your type
> is unique.  And it won't tell you there's a problem if you mistakenly
> type origin=="SQL" instead of "sql".

The module name can supplant that.

> I think it boils down to a kind of static typing vs dynamic typing argument.

To some extent, yes. But, I think an adept of static typing should also recognize when it brings nothing to the table, and IMHO exceptions are one of those cases.


Andrei
October 23, 2008
"Andrei Alexandrescu" wrote
> Steven Schveighoffer wrote:
>> "Andrei Alexandrescu" wrote
>>> Sean Kelly wrote:
>>>> Andrei Alexandrescu wrote:
>>>>> Robert Fraser wrote:
>>>>>> Option B:
>>>>>> ---------
>>>>>> try
>>>>>> {
>>>>>>     new Socket(30587);
>>>>>> }
>>>>>> catch(Exception e)
>>>>>> {
>>>>>>     if(e.type == ExceptionType.Socket)
>>>>>>         printf("Could not open socket\n");
>>>>>>     else
>>>>>>         throw e;
>>>>>> }
>>>>> I think you'd be hard-pressed to justify the "if" inside the second example. You couldn't create a Socket, period. It doesn't matter where exactly the exception was generated from.
>>>>>
>>>>> That's one thing about large exception hierarchies: everybody can come with cute examples on how they could be useful. As soon as the rubber hits the road, however, differentiating exceptions by type becomes useless.
>>>> It may be different in a user application, but in services it's fairly common to have specialized code for handling different exception types. And more importantly, it's common to want different exception types to propagate to different levels for handling.  Sure, one could use a generic exception handler at each level that rethrows if the detected type isn't one that handler cares about but why do this when filtering on type is a language feature?
>>>>
>>>> For example, let's say I have a network service that's backed by a SQL database.  My main program loop may look something like this:
>>>>
>>>>     bool connected = false;
>>>>     while( true )
>>>>     {
>>>>         try
>>>>         {
>>>>             while( true )
>>>>             {
>>>>                 auto r = acceptRequest();
>>>>                 scope(failure) r.tellFailed();
>>>>                 if( !connected )
>>>>                     connectToDB();
>>>>                 handleRequest( r );
>>>>             }
>>>>         }
>>>>         catch( SqlException e )
>>>>         {
>>>>             connected = false;
>>>>             log( e );
>>>>         }
>>>>         catch( Exception e )
>>>>         {
>>>>             log( e );
>>>>         }
>>>>     }
>>>>
>>>>     ...
>>>>
>>>>     void handleRequest( Request r )
>>>>     {
>>>>         scope(failure) r.tellFailed( "General error" );
>>>>
>>>>         try
>>>>         {
>>>>             // process r
>>>>         }
>>>>         catch( AuthException e )
>>>>         {
>>>>             r.tellFailed( "Authentication failure" );
>>>>         }
>>>>         catch( ValidationException e )
>>>>         {
>>>>             r.tellFailed( "Invalid request format" );
>>>>         }
>>>>     }
>>>>
>>>> Being able to trap specific types of exceptions makes this code cleaner and more succinct than it would be otherwise.  If this weren't possible I'd have to trap, check, and rethrow certain exceptions at different levels to ensure that the proper handler saw them.
>>> Thanks for fueling my argument. There's duplication in code examples, as in many other examples I've seen in favor of by-type handling.
>>>
>>> First example:
>>>
>>>         catch( Exception e )
>>>         {
>>>             if (e.origin = "sql") connected = false;
>>>             log( e );
>>>         }
>>>
>>> Less code and no duplication. Second example is even starker:
>>>
>>>         catch( AuthException e )
>>>         {
>>>             r.tellFailed( e.toString );
>>>         }
>>>
>>> Clearly the need is to factor in the message to print in the exception, at least in this case and many like it.
>>
>> For the second example, you are assuming that ValidationException is a subclass of AuthException.
>
> Sorry, I meant to catch Exception. My point was, if I want to print an informative message, the exception should be able to provide it without having to encode it in its type.

Sure, but then your example becomes:

catch(Exception e)
{
   if(e.reason is Auth || e.reason is Validation)
      r.tellFailed(e.toString());
   else
      throw e;
}

Not that it looks terrible, but I just want it to be clear that your one-liner isn't sufficient.

>
>> I think in this case, ValidationException and AuthException might share a common parent (SqlException) which you do NOT want to catch.  It is advantageous in this case to have both a hierarchy through inheritance and a hierarchy through parameters.
> >
>> Here's another idea, to avoid rethrows, what about some pre-handler code that tests if an exception is what you want?  Perhaps the same mechanism that is used by template constraints:
>>
>> catch(SqlException e) if (e.reason is Auth || e.reason is Validation)
>> {
>>    r.tellFailed(e.toString());
>> }
>
> I think we're a bit too immersed in the "what can we add to the language". In this case, for example, I see no reason for the "if" to be moved inside and call it a day.

A fair point.  It might help in situations like this (with your philosophy of not subclassing for specific reasons):

try
{
  try
  {
     ...
  }
  catch(SqlException sql)
  {
     if(sql.reason == Auth || sql.reason == Validation)
        changeUsertokensAndRetry(); // recovery
     else
        throw sql;
  }
}
catch(Exception e)
{
   r.tellFailed(e.toString());
}

Would then become:

try
{
   ...
}
catch(SqlException sql) if (sql.reason == Auth || sql.reason == Validation)
{
   changeUsertokensAndRetry();
}
catch(Exception e)
{
  r.tellFailed(e.toString());
}

instead of one giant catch block that catches all exceptions.

Basically, it augments the code that determines where the exception is caught to check for more than just type.

But I don't know how common stuff like that is.

-Steve


October 23, 2008
Thu, 23 Oct 2008 14:44:46 -0500,
Andrei Alexandrescu wrote:
> Sorry, I meant to catch Exception. My point was, if I want to print an informative message, the exception should be able to provide it without having to encode it in its type.

It's all nice and simple until you want to localize.  Then all your 'informative compiler-provided messages' appear as garbage to a non- English-speaking user.  They need to be translated somehow.  And the translation is likely to be based upon the context, not upon an arbitrary string exception contains.
October 23, 2008
On Fri, Oct 24, 2008 at 6:41 AM, Sergey Gromov <snake.scaly@gmail.com> wrote:
> Thu, 23 Oct 2008 14:44:46 -0500,
> Andrei Alexandrescu wrote:
>> Sorry, I meant to catch Exception. My point was, if I want to print an informative message, the exception should be able to provide it without having to encode it in its type.
>
> It's all nice and simple until you want to localize.  Then all your 'informative compiler-provided messages' appear as garbage to a non- English-speaking user.  They need to be translated somehow.  And the translation is likely to be based upon the context, not upon an arbitrary string exception contains.

Just use poedit or whatever to provide localizations of the messages. I don't see how this has bearing on the discussion at all.

--bb
October 23, 2008
Fri, 24 Oct 2008 06:55:27 +0900,
Bill Baxter wrote:
> On Fri, Oct 24, 2008 at 6:41 AM, Sergey Gromov <snake.scaly@gmail.com> wrote:
> > Thu, 23 Oct 2008 14:44:46 -0500,
> > Andrei Alexandrescu wrote:
> >> Sorry, I meant to catch Exception. My point was, if I want to print an informative message, the exception should be able to provide it without having to encode it in its type.
> >
> > It's all nice and simple until you want to localize.  Then all your 'informative compiler-provided messages' appear as garbage to a non- English-speaking user.  They need to be translated somehow.  And the translation is likely to be based upon the context, not upon an arbitrary string exception contains.
> 
> Just use poedit or whatever to provide localizations of the messages. I don't see how this has bearing on the discussion at all.

Where do I get all the messages library may throw?  How can I be sure that the message won't change in the next release, silently discarding my translation?

Informative library-provided messages are a myth.  The only informative part is an exception type, or a standardized error code--which is even better for localization.  The "writeln(e.toString())" approach only suffices for quick utilities not meant for use by an end user.
October 23, 2008
On Fri, Oct 24, 2008 at 7:45 AM, Sergey Gromov <snake.scaly@gmail.com> wrote:
> Fri, 24 Oct 2008 06:55:27 +0900,
> Bill Baxter wrote:
>> On Fri, Oct 24, 2008 at 6:41 AM, Sergey Gromov <snake.scaly@gmail.com> wrote:
>> > Thu, 23 Oct 2008 14:44:46 -0500,
>> > Andrei Alexandrescu wrote:
>> >> Sorry, I meant to catch Exception. My point was, if I want to print an informative message, the exception should be able to provide it without having to encode it in its type.
>> >
>> > It's all nice and simple until you want to localize.  Then all your 'informative compiler-provided messages' appear as garbage to a non- English-speaking user.  They need to be translated somehow.  And the translation is likely to be based upon the context, not upon an arbitrary string exception contains.
>>
>> Just use poedit or whatever to provide localizations of the messages. I don't see how this has bearing on the discussion at all.
>
> Where do I get all the messages library may throw?

Ah, I see your point now.   I thought this was still about making
distinct types for every exception.
I missed the comment from Andrei.

--bb
October 23, 2008
Sergey Gromov wrote:
> Thu, 23 Oct 2008 14:44:46 -0500,
> Andrei Alexandrescu wrote:
>> Sorry, I meant to catch Exception. My point was, if I want to print an informative message, the exception should be able to provide it without having to encode it in its type.
> 
> It's all nice and simple until you want to localize.  Then all your 'informative compiler-provided messages' appear as garbage to a non-
> English-speaking user.  They need to be translated somehow.  And the translation is likely to be based upon the context, not upon an arbitrary string exception contains.

No, translation would have to be provided via a string table. And guess who provides the key for that table.

Andrei
October 23, 2008
Sergey Gromov wrote:
> Fri, 24 Oct 2008 06:55:27 +0900,
> Bill Baxter wrote:
>> On Fri, Oct 24, 2008 at 6:41 AM, Sergey Gromov <snake.scaly@gmail.com> wrote:
>>> Thu, 23 Oct 2008 14:44:46 -0500,
>>> Andrei Alexandrescu wrote:
>>>> Sorry, I meant to catch Exception. My point was, if I want to print an
>>>> informative message, the exception should be able to provide it without
>>>> having to encode it in its type.
>>> It's all nice and simple until you want to localize.  Then all your
>>> 'informative compiler-provided messages' appear as garbage to a non-
>>> English-speaking user.  They need to be translated somehow.  And the
>>> translation is likely to be based upon the context, not upon an arbitrary
>>> string exception contains.
>> Just use poedit or whatever to provide localizations of the messages.
>> I don't see how this has bearing on the discussion at all.
> 
> Where do I get all the messages library may throw?  How can I be sure that the message won't change in the next release, silently discarding my translation?
> 
> Informative library-provided messages are a myth.  The only informative part is an exception type, or a standardized error code--which is even better for localization.  The "writeln(e.toString())" approach only suffices for quick utilities not meant for use by an end user.

I agree. For true localization, string tables are necessary.

Andrei