incubator-kato-spec mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Nicholas Sterling <Nicholas.Sterl...@Sun.COM>
Subject Re: Kato API javadoc - error handling
Date Wed, 15 Apr 2009 19:21:07 GMT
I'm finding this a very useful exercise.

May I suggest a couple of minor tweaks:

    ObjectVisitor objVisitor = new OurObjectVisitor() {
        boolean visitField( JavaClass clazz, JavaField field, Object
    value ) {
            System.out.println(
                          obj.getID()
                + ": "  + clazz.getName()
                + "."   + field.getSignature()
                + " "   + field.getName()
                + " = " + value
            );
            return true;
        }
    };
    HeapVisitor visitor = new OurHeapVisitor() {
        boolean visitObject( JavaObject obj )  {
            obj.visit(objVisitor);
            return true;
        }
    };

That is, a single ObjectVisitor, created beforehand, can be used with 
each object.  Also, you probably want the object visitor to extend a 
customized OurObjectVisitor that handles exceptions.

Presumably we would want the visitors to return true, to keep going.  
Alternatively we could supply a stopVisiting() method which the visitor 
could call, and not return the boolean.

Nicholas


Stuart Monteith wrote:
> I think 1. would be rejected on the basis that errors are common.
> However, 3. would be accepted because errors are common i.e. most of 
> the time they are logged and ignored.
>
> One thing I like about the Visitor pattern is that it is never left to 
> the caller of the API to decide under what circumstances it should 
> stop retreiving objects, the expectation is entirely on the API 
> implementer to be able to complete. It's not the case that the API 
> implementer might mistakenly place the responsibility for halting onto 
> the API caller.
>
> The next level to examine the Visitor pattern would be to look at 
> JavaClass and JavaField. The example I posted was of printing out all 
> of the fields of all of the objects on the heap. The gnarly thing 
> about this is you have to go through getting the Object, then the 
> class, then the field, then applying them to the object.
>
> Would this be sensible?:
>
> interface ObjectVisitor {
>    public void visitField(JavaClass clazz, JavaField field, Object 
> value);
> }
>
>       HeapVisitor visitor = new _OurHeapVisitor_() {
>           boolean visitObject( JavaObject obj )  {           
>              ObjectVisitor objVisitor = new ObjectVisitor() {
>                   public void visitField(JavaClass clazz, JavaField 
> field, Object value) {
>                      System.out.println(obj.getID()+": 
> "+clazz.getName()+"."+field.getSignature()+" "+field.getName()+" = 
> "+value);
>                   }
>               };
>               obj.visit(objVisitor);
>           }
>       };
>
>
>
> Nicholas Sterling wrote:
>> I was just looking closer at the example code, and wondering about 
>> the location of the try-catch.  Just to make sure we're on the same 
>> page, I'd like to throw out some different possibilities for the way 
>> you would actually handle exceptions using the visitor approach.
>>
>> 1. Here we (probably unwisely) use the traditional approach, and 
>> abort heap traversal if there is an error of any kind:
>>
>>    void doObjects() {
>>
>>        HeapVisitor visitor = new HeapVisitor() {
>>            boolean visitObject( JavaObject obj )  {
>>                // do something; return true to keep going
>>            }
>>        };
>>
>>        try{
>>            JavaHeap heap = factory.getJavaHeap(...);
>>            heap.visit( visitor );
>>        } catch ( CorruptDataException e ) {
>>            ...
>>        } catch ( DataUnavailableException e ) {
>>            ...
>>        }
>>    }
>>
>> 2. Here we handle the error inside the visitor and keep going:
>>
>>    void doObjects() _throws KatoException_ {
>>
>>        HeapVisitor visitor = new HeapVisitor() {
>>            boolean visitObject( JavaObject obj )  {
>>                // do something; return true to keep going
>>            }
>>            void handleDataUnavailable( JavaObject obj ) {
>>                ...
>>            }
>>            void HandleCorruptDataException(JavaObject obj) {
>>                ...
>>            }
>>        };
>>
>>        JavaHeap heap = factory.getJavaHeap(...);
>>        heap.visit( visitor );
>>    }
>>
>> 3. Here we abstract out that error-handling into a subclass of 
>> HeapVisitor:
>>
>>    class OurHeapVisitor extends HeapVisitor {
>>            void handleDataUnavailable( JavaObject obj ) {
>>                ...
>>            }
>>            void HandleCorruptDataException(JavaObject obj) {
>>                ...
>>            }
>>    }
>>    ------------------------------------------------------------
>>    void doObjects() _throws KatoException_ {
>>
>>        HeapVisitor visitor = new _OurHeapVisitor_() {
>>            boolean visitObject( JavaObject obj )  {
>>                // do something; return true to keep going
>>            }
>>        };
>>
>>        JavaHeap heap = factory.getJavaHeap(...);
>>        heap.visit( visitor );
>>    }
>>
>> This third one, in particular, is highly readable.
>>
>> Nicholas
>>
>>
>> Stuart Monteith wrote:
>>> I think with the visitor pattern we'd have to weigh up the typical 
>>> usage patterns against the loss of control it implies.
>>> Certainly for the heap, you might typically do a linear scan as the 
>>> locality of objects implies nothing about their relationship to one 
>>> another - using a bidirectional cursor is pointless.. The loss of 
>>> control means that the entire heap will be scanned, regardless of 
>>> what occurs.
>>>
>>> I think better with examples, so say we are counting all of the 
>>> instances of classes, by name:
>>>
>>>
>>>   final Foo outside_class = ...;
>>>
>>>   HeapVisitor visitor = new HeapVisitor() {
>>>       HashMap<String,Long> classCount = new HashMap<String,Long>();
>>>       Foo inside_class = ...;
>>>
>>>       void visitObject( JavaObject obj )  {
>>>           // Both outside_class and inside_class are visible.
>>>           // Var outside_class cannot be modified, of course,
>>>           // but fields in the object it refers to may.
>>> 1         JavaClass clazz = obj.getJavaClass();
>>> 2         String className = clazz.getName();
>>>           Long count= classCount.get(className);
>>>                  if (count.longValue() == 0) {
>>>             classCount.put(className, 1);
>>>          } else {
>>>             classCount.put(className, count+1);
>>>          }
>>>       }
>>>
>>>       void handleDataUnavailable( JavaObject obj ) {
>>>           // Same here.
>>>           // If we didn't put this method here, the default
>>>           // would just throw a DataUnavailableException.
>>>       }
>>>            void HandleCorruptDataException(JavaObject obj) {
>>>       }
>>>   };
>>>
>>>    try{
>>>       JavaHeap heap = factory.getJavaHeap(...);
>>>    }catch(CorruptDataException e) {
>>>    }catch(DataUnavailableException e) {
>>>    }
>>>   heap.visit( visitor );
>>>
>>> We could get CorruptDataException at 1 and 2, one would be because 
>>> of the JavaObject, the other would be because of the JavaClass it 
>>> referred to. I would posit that the exception information should be 
>>> sufficient to properly report the problem in most cases as the 
>>> processing done in a visitor method would be to do only with the 
>>> object it is passed, and anything fairly close to it, such as its 
>>> classes and fields.
>>>
>>> Of course, how well would this work for smaller items, such as 
>>> fields in classes, classes in classloaders, etc.
>>> With fields, I tend to want to address them by name or get all of 
>>> them from a class.
>>>
>>>
>>>
>>> Nicholas Sterling wrote:
>>>> That last example, the JDBC sanitizer, is much more conventional -- 
>>>> there's no handler stack.  It is essentially a combination of the 
>>>> Template and Visitor design patterns.  The Template pattern is 
>>>> often applied to Java exceptions, so I don't think this would be 
>>>> thought of as unusual.
>>>>
>>>> Instance variables in the anonymous class would be accessible to 
>>>> both the method doing the work and the method handling the 
>>>> exceptions.  And the class could access final variables outside it:
>>>>
>>>>    final Foo outside_class = ...;
>>>>
>>>>    HeapVisitor visitor = new HeapVisitor() {
>>>>
>>>>        Foo inside_class = ...;
>>>>
>>>>        void visitObject( JavaObject obj ) {
>>>>            // Both outside_class and inside_class are visible.
>>>>            // Var outside_class cannot be modified, of course,
>>>>            // but fields in the object it refers to may.
>>>>        }
>>>>
>>>>        void handleDataUnavailable( JavaObject obj ) {
>>>>            // Same here.
>>>>            // If we didn't put this method here, the default
>>>>            // would just throw a DataUnavailableException.
>>>>        }
>>>>    };
>>>>
>>>>    JavaHeap heap = factory.getJavaHeap(...);
>>>>    heap.visit( visitor );
>>>>
>>>> Does something like that seem reasonable?
>>>>
>>>> Nicholas
>>>>
>>>>
>>>>
>>>> Daniel Julin wrote:
>>>>> It seems to me that with all this discussion of polymorphic and 
>>>>> stackable
>>>>> error handlers, we're rapidly re-inventing much of the Java exception
>>>>> facility, and probably rediscovering a lot of the same design
>>>>> considerations that the original designers of that Java exception 
>>>>> facility
>>>>> faced...
>>>>>
>>>>> As far as I can tell, the one major difference is that, with Java
>>>>> exceptions, once an exception is thrown, the flow of control is 
>>>>> irrevocably
>>>>> interrupted and can only resume after the exception handler, not 
>>>>> back at
>>>>> the point where the exception was thrown. This means that if you 
>>>>> do want
>>>>> fine control over your errors but don't want to interrupt a whole 
>>>>> sequence
>>>>> of operations, you're forced to put an explicit try/catch block at 
>>>>> every
>>>>> line of the program. Whereas with these proposed handlers, a single
>>>>> top-level handler could presumably take care of all the errors in a
>>>>> sequence of operations without interrupting that sequence.
>>>>>
>>>>> Is that a correct interpretation? And is there no practical way to 
>>>>> achieve
>>>>> the same effect with actual Java exceptions? Some sort of "continue"
>>>>> statement inside a catch block, maybe?
>>>>>
>>>>>
>>>>> On the other hand, one thing that Java try/catch blocks offer is 
>>>>> controlled
>>>>> access to the local variables of the method in which the try/catch 
>>>>> block
>>>>> resides, and to the private instance members of the object. The 
>>>>> proposed
>>>>> handlers, since they are executing in a separate object and a 
>>>>> separate
>>>>> method, would not enjoy a similar access. Whether this limitation 
>>>>> would
>>>>> prove bothersome in practice, will depend on the actual usage 
>>>>> scenarios
>>>>> that we face...
>>>>>
>>>>>
>>>>> -- Daniel --
>>>>>
>>>>>
>>>>>
>>>>> Nicholas.Sterling@Sun.COM wrote on 2009-04-14 05:39:40 PM:
>>>>>  
>>>>>> If we were to end up doing something like this, would we use 
>>>>>> checked or
>>>>>> unchecked exceptions?  If we use checked exceptions, the client will
>>>>>> still have to catch the exception or say that the method throws it.
>>>>>> Presumably that would defeat the purpose...
>>>>>>
>>>>>> Good idea on the comparison code.  I'm a little concerned that 
>>>>>> people
>>>>>> may think we are overengineering the whole exception thing; once

>>>>>> we have
>>>>>> the comparison code, we can run it by some folks and see whether

>>>>>> their
>>>>>> brains short-circuit.
>>>>>>
>>>>>> It might be helpful as a reference point to consider an example with
>>>>>> another API.  I was sufficiently frustrated by the unreadability

>>>>>> of my
>>>>>> JDBC clients that I wrote a pair of classes, Query and Querier, that
>>>>>> hide gory details, and I think it makes a big difference.  Among
the
>>>>>> hidden are the binding of variables and iterating through result

>>>>>> sets,
>>>>>> but probably the biggest benefit is from hiding the 
>>>>>> exception-handling
>>>>>> logic (it closes the ResultSet for you on an exception). This is

>>>>>> what it
>>>>>> looks like to use it:
>>>>>>
>>>>>>     // Create a query to get recent bugs.
>>>>>>     static final Query recent_bugs_query = new Query(
>>>>>>         "bugs submitted against a PRODUCT in the last NUM DAYS",
>>>>>>         "select id, synopsis from bugs " +
>>>>>>        " where product = ? and date_submitted > sysdate - ?"
>>>>>>     );
>>>>>>     ...
>>>>>>     // Given a Connection conn and values for PRODUCT and NUM DAYS,
>>>>>>     // query the DB for recent bugs and display the resulting rows.
>>>>>>     new Querier( recent_bugs_query, conn, product, num_days ) {
>>>>>>         public void doRow() throws SQLException {
>>>>>>             System.out.println( rs.getString(1) + " " + rs.getString
>>>>>>     
>>>>> (2) );
>>>>>  
>>>>>>         }
>>>>>>     }
>>>>>>
>>>>>> A major benefit was getting rid of that awful doubly-nested catch

>>>>>> block
>>>>>> (closing the ResultSet in the catch block may throw an exception,

>>>>>> so it
>>>>>> requires its own try-catch -- gaah!).
>>>>>>
>>>>>> The default Querier throws an exception, but you can extend 
>>>>>> Querier and
>>>>>> override the handleException() method to do whatever is 
>>>>>> appropriate for
>>>>>> your app, and they use your custom Querier throughout your 
>>>>>> program, e.g.
>>>>>>
>>>>>>     class MyQuerier extends Querier {
>>>>>>         void handleException( Exception ex ) {
>>>>>>             ....
>>>>>>         }
>>>>>>     }
>>>>>>
>>>>>> Perhaps we could use a similar approach, for example providing a
>>>>>> HeapQuerier class from which clients create anonymous classes to

>>>>>> do what
>>>>>> they want.
>>>>>>
>>>>>> Nicholas
>>>>>>
>>>>>>
>>>>>>
>>>>>> Steve Poole wrote:
>>>>>>  
>>>>>>> On Mon, Apr 13, 2009 at 4:34 AM, Nicholas Sterling <
>>>>>>> Nicholas.Sterling@sun.com> wrote:
>>>>>>>
>>>>>>>
>>>>>>>   
>>>>>>>> And a Handler (whatever it should really be called) would
have 
>>>>>>>> access
>>>>>>>>         
>>>>> to
>>>>>  
>>>>>>>> the previous Handler on the stack, so it could do
>>>>>>>>
>>>>>>>>   void handleJavaObjectUnavailable(...) {
>>>>>>>>       // do some stuff, then defer to the guy beneath us
on the 
>>>>>>>> stack:
>>>>>>>>       prevHandler().handleJavaObjectUnavailable(...);
>>>>>>>>   }
>>>>>>>> Nicholas
>>>>>>>>
>>>>>>>> This is cool -  The callback approach is sort of a half way
>>>>>>>>         
>>>>> housebetween a
>>>>>  
>>>>>>> DOM and SAX model.  It could allow us to have a default "no nulls"
>>>>>>>       
>>>>> approch
>>>>>  
>>>>>>> for an implementation but still allows for users of the API to
do
>>>>>>>       
>>>>> something
>>>>>  
>>>>>>> different.
>>>>>>>
>>>>>>> I think we should create some comparison code segments to see

>>>>>>> what it
>>>>>>>       
>>>>> could
>>>>>  
>>>>>>> look like.
>>>>>>>
>>>>>>>
>>>>>>>   
>>>>>>>> Nicholas Sterling wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>>     
>>>>>>>>> Daniel Julin wrote:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>       
>>>>>>>>>> I like that approach a lot, because it may also address
the 
>>>>>>>>>> other
>>>>>>>>>>             
>>>>> concern
>>>>>  
>>>>>>>>>> that a proposed "default reasonable behavior" may
not be 
>>>>>>>>>> appropriate
>>>>>>>>>>             
>>>>> for
>>>>>  
>>>>>>>>>> all usage scenarios. We could probably come-up with
a variety of
>>>>>>>>>>             
>>>>> handlers
>>>>>  
>>>>>>>>>> for various common behaviors, like printing a simple
error 
>>>>>>>>>> message,
>>>>>>>>>> completely ignoring the error, and lots of other
creative 
>>>>>>>>>> responses.
>>>>>>>>>>
>>>>>>>>>> Incidentally, nothing in this discussion is particularly

>>>>>>>>>> specific to
>>>>>>>>>>             
>>>>> the
>>>>>  
>>>>>>>>>> Kato API, is it? Are we saying that, in general,
we don't like
>>>>>>>>>>             
>>>>> exceptions
>>>>>  
>>>>>>>>>> as the standard mechanism to report errors in Java,
and that 
>>>>>>>>>> we're
>>>>>>>>>> inventing new patterns?  If so, have any useful patterns
been
>>>>>>>>>>             
>>>>> proposed
>>>>>  
>>>>>>>>>> and
>>>>>>>>>> documented previously in the literature?
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>             
>>>>>>>>> I just looked around a little, and am only seeing suggestions

>>>>>>>>> for how
>>>>>>>>>           
>>>>> the
>>>>>  
>>>>>>>>> *client* can abstract out the exception-handling logic
using the
>>>>>>>>>           
>>>>> Template
>>>>>  
>>>>>>>>> design pattern.  So far I haven't seen any advice for
API 
>>>>>>>>> designers.
>>>>>>>>>
>>>>>>>>> By the way, it occurred to me that the setter can have
a 
>>>>>>>>> generic name
>>>>>>>>> because overloading will allow us to have a method for
each
>>>>>>>>>           
>>>>> condition:
>>>>>  
>>>>>>>>>   factory.setHandler( new DataUnavailableHandler( ...
) {
>>>>>>>>>       ...
>>>>>>>>>   } );
>>>>>>>>>
>>>>>>>>> Also, it might make sense to push the handler on a stack
rather
>>>>>>>>>           
>>>>> replace
>>>>>  
>>>>>>>>> what is there.  That will allow independent modules to
modify 
>>>>>>>>> just
>>>>>>>>>           
>>>>> that
>>>>>  
>>>>>>>>> behavior they need to and then remove those modifications
when 
>>>>>>>>> they
>>>>>>>>>           
>>>>> are no
>>>>>  
>>>>>>>>> longer needed.  It also means that we can have just one
Handler
>>>>>>>>>           
>>>>>> () class for
>>>>>>  
>>>>>>>>> all the handlers, e.g.
>>>>>>>>>
>>>>>>>>>   // Temporarily override the handling of DataUnavailable
errors.
>>>>>>>>>   factory.pushHandler( new Handler( ... ) {
>>>>>>>>>       void handleJavaObjectUnavailable(...) {
>>>>>>>>>           // handling specific for JavaObjects
>>>>>>>>>       }
>>>>>>>>>       void handleDataUnavailable(...) {
>>>>>>>>>           // handling for all other DataUnavailable conditions
>>>>>>>>>       }
>>>>>>>>>       // All handler methods not overridden will simply
call 
>>>>>>>>> the same
>>>>>>>>> method
>>>>>>>>>       // for the object beneath us on the stack.  If
we get to 
>>>>>>>>> the
>>>>>>>>>           
>>>>> bottom,
>>>>>  
>>>>>>>>> the
>>>>>>>>>       // handler there will throw an exception.
>>>>>>>>>   } );
>>>>>>>>>   // Do some work that might cause an exception.  This
might 
>>>>>>>>> include
>>>>>>>>> calling
>>>>>>>>>   // an independently written module that also wants
to 
>>>>>>>>> temporarily
>>>>>>>>> override
>>>>>>>>>   // some handler, but they will pop that before returning
to us.
>>>>>>>>>   factory.popHandler();
>>>>>>>>>
>>>>>>>>> Nicholas
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>       
>>>>>>>>>> -- Daniel --,
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Nicholas.Sterling@Sun.COM wrote on 2009-04-11 01:48:53
AM:
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>         
>>>>>>>>>>> Daniel Julin wrote:
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>           
>>>>>>>>>>>> I guess a two mode approach would make everyone
happy. But 
>>>>>>>>>>>> would
>>>>>>>>>>>>                 
>>>>> it
>>>>>  
>>>>>>>>>>>>
>>>>>>>>>>>>                 
>>>>>>>>>>> make
>>>>>>>>>>>
>>>>>>>>>>>               the API too complicated?
>>>>>>>>>>>
>>>>>>>>>>>           
>>>>>>>>>>>>
>>>>>>>>>>>>                 
>>>>>>>>>>> I have some sympathy for what Steve is talking
about -- 
>>>>>>>>>>> maybe my
>>>>>>>>>>> short-term memory is small, but when lots of
single lines of 
>>>>>>>>>>> code
>>>>>>>>>>> inflate to 6 lines (and two indentation levels),
it is 
>>>>>>>>>>> definitely
>>>>>>>>>>>               
>>>>> harder
>>>>>  
>>>>>>>>>>> for me to read.  However, I wouldn't want to
give up the 
>>>>>>>>>>> certain
>>>>>>>>>>>               
>>>>> and
>>>>>  
>>>>>>>>>>> immediate catching of errors offered by exceptions.
>>>>>>>>>>>
>>>>>>>>>>> Would a mechanism like this work for the two-mode
approach?
>>>>>>>>>>>
>>>>>>>>>>>    factory.setDataUnavailableHandler( new 
>>>>>>>>>>> DataUnavailableHandler
>>>>>>>>>>>               
>>>>> ( ... )
>>>>>  
>>>>>>>>>>>
>>>>>>>>>>>               
>>>>>>>>>> {
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>         
>>>>>>>>>>>        ...
>>>>>>>>>>>    } );
>>>>>>>>>>>
>>>>>>>>>>> All objects created by the factory would call
that object's
>>>>>>>>>>> dataUnavailable() method when appropriate, passing
it enough 
>>>>>>>>>>> info
>>>>>>>>>>>               
>>>>> about
>>>>>  
>>>>>>>>>>> what was going on to allow the method to make
interesting 
>>>>>>>>>>> decisions
>>>>>>>>>>> about what to do.  The default handler would
always throw a
>>>>>>>>>>> DataUnavailableException.
>>>>>>>>>>>
>>>>>>>>>>> It's hard for me to tell whether something like
that would 
>>>>>>>>>>> really
>>>>>>>>>>> suffice in actual use.  Perhaps it would have
to be 
>>>>>>>>>>> finer-grained,
>>>>>>>>>>>               
>>>>> with
>>>>>  
>>>>>>>>>>> methods for javaObjectUnavailable(), imageSectionUnavailable(),
>>>>>>>>>>>               
>>>>> etc.
>>>>>  
>>>>>>>>>>> Perhaps the defaults for those would call the
more generic
>>>>>>>>>>> dataUnavailable() so that you could intervene
for all cases 
>>>>>>>>>>> and/or
>>>>>>>>>>>               
>>>>> for
>>>>>  
>>>>>>>>>>> individual cases as desired.
>>>>>>>>>>>
>>>>>>>>>>> Nicholas
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>               
>>>>>>>>>>             
>>>>>>>       
>>>>>
>>>>>   
>>>>
>>

Mime
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message