incubator-kato-spec mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Stuart Monteith <stuk...@stoo.me.uk>
Subject Re: Kato API javadoc - error handling
Date Wed, 15 Apr 2009 17:52:23 GMT
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
View raw message