Skip to main content
    Country/region select      Terms of use
     Home      Products      Services & solutions      Support & downloads      My account     

developerWorks  >  Lotus  >  Forums & community  >  Best Practice Makes Perfect

Best Practice Makes Perfect

A collaboration with Domino developers about how to do it and how to get it right in Domino

I've been writing some LotusScript classes recently, and it occurred to me to write about this technique for improving performance --  hope it's not too obvious.

Frequently, classes have some properties you're not sure the caller will use. A lot of things, you would initialize in the constructor ("Sub New"), so that they will be available when needed. But others, which might be expensive to calculate, you'd like to delay calculating them until someone asks for them (the computer version of "slack"). But, if someone asks for them twice, you don't want to do the expensive operation twice.
The way I've been doing this, is to set up a "Property Get" for the item, and have a private member where the value is stored the first time it's calculated. For instance:

Const ERR_HM_NOPADDOCK = 18651
Const PADDOCK_VIEW_NAME = "PaddocksByTitle"

Class HorseManager
  m_db As NotesDatabase ' initially Nothing
  m_paddockView As NotesView ' ditto
  Public Property Get Database( )
     If m_db Is Nothing Then
        Set m_db = New NotesDatabase(m_server, m_filepath)
        If Not m_db.IsOpen Then
           Error ERR_HM_NODATABASE, "Error in HorseManager.Database: can't open '" & m_filepath & "' on '" & m_server & "'"
        End If
     End If
     Set Database = m_db
  End Property
  Public Property Get PaddockView() As NotesView
     If m_paddockView Is Nothing Then
        Set m_paddockView = Database.GetView(PADDOCK_VIEW_NAME)
        If m_paddockView Is Nothing Then
           Error ERR_HM_NOPADDOCK, "Error in HorseManager.PaddockView: view '" & PADDOCK_VIEW_NAME & "' not found."
        End If
     End If
     Set PaddockView = m_paddockView
  End Property
End Class

The property routine Database does the work of opening the database only once, the first time it's called. Thereafter, it notices the database is already stored in the private property m_db, and just returns the value from there. The PaddockView computed property works the same way, but since it uses the Database property internally, it might end up doing nothing (if the view has been requested before), or it might look up the view, or it might open the database and look up the view, if the database wasn't opened yet. Obviously we can continue the chain, e.g. we might have a GetPaddock(paddockName$) method that uses the PaddockView property to get its lookup view. And the on-demand properties don't have to be Public, either -- I've had occasion to use them internally in the class for values we don't need to expose to the world.

Notice how I throw errors if there's a problem computing the requested value. This is preferable to just returning a value that's not usable, because we explain the problem at the real point of failure. If PaddockView just returned Nothing in case of a nonexistent view, the caller wouldn't see that there was a problem until it tried to use the returned value, getting the much less informative error "Object variable not set." The caller of course has the option of trapping (or ignoring) the error and taking corrective action in either case, or test the return value of PaddockView before using it, but since this code provides a unique error number, it's easier to code something that addresses the specific issue or displays a useful message to the user. E.g. if PaddockView returns Nothing, you might want to know whether the problem is with the database or the view.

The price we pay for not having to calculate the values each time they are needed, is that we have to remember to clear the cached values should they become invalid. I generally define a private method to erase the cached data, which I call from various places in other methods when a relevant value changes (there might be more than one such routine, if there are a bunch of cached values that aren't all necessarily invalidated at the same time).

  Private Sub ClearCache
     Set m_db = Nothing
     Set m_paddockView = Nothing
  End Sub
  Public Property Set Filepath As String
     m_filepath = Filepath
     ClearCache ' any existing database and view objects are no longer valid
  End Property

It makes sense to use this technique if the operation that produces the data item is relatively expensive, and if it's possible that the value will not be required by the code that uses the class. In many cases, though, I've found that even aside from performance considerations, it results in cleaner looking code. You don't always have all the information you need to initialize everything in the constructor, and by using this technique, you know you don't have to worry that everything will be assigned in the right order for you to use it -- by using your own property routine internally, it's assigned at the time that you require it, if not before.
Of course, it's a little more expensive and more lines of code to call a property function as opposed to referring to a property member directly, but if they are read-only properties, you need the Property Get function anyway, and the performance cost is small compared to opening a database or view unnecessarily.

Andre Guirard | 19 September 2007 01:39:00 PM ET | Nokomis Beach Coffee, Minneapolis, MN | Comments (1)


1) it may be intuitively obvious, but i’m glad you said it...
Brandt S Fundak | 9/19/2007 3:57:33 PM

I'm glad to see you discussing this...i recently wrote a wrapper class for the LCConnection object of the LSX for Lotus Connectors class. I realized that when I was connecting to my outside data source I was always doing the same thing, so I wrote a class that basically only serves to connect to the outside data source. The nice thing about this is that if I don't need to connect to my outside data source, I don't have to, and it also makes the code highly portable to any other databases where I need to connect to that data source.

of course, I probably should re-write it so it works with any external data connection, but I'll cross that bridge when I get to it...

2) Lazy Initalization
Peter Leugner | 9/19/2007 4:48:11 PM

{ Link }

3) does anyone *not* do this?
Charles Robinson | 9/19/2007 4:56:29 PM

Early in my OOP development it was bludgeoned into me to only instantiate objects I need. I still go by that, even with things like NotesSession that I know is a singleton.

@1 - I did that, too, and eventually dropped it into a LSS to make it even easier for me to use. Now it's just one line to include it in any database, and debugging happens in one file and is instantly updated everywhere.

4) not only good for OOP
Sean Burgess | 9/20/2007 10:32:10 AM

This technique is not only something that should be used for OOP. I can't tell you how many times I have see LotusScript agents with a bunch of subs and they are all getting a handle to the CurrentDatabase or opening a view each time it's called. While I am not a fan of using global variables too much, this is where they are really useful. Either set the view to a global variable (after checking if it's already been set or not) or pass in the view from the initialize event and set it once there.

5) re: not only good for OOP
Andre Guirard | 9/20/2007 12:06:50 PM

Yes, or you can declare the object in question as a static variable in the routine you use to fetch it.

Function GetUsefulView( ) As NotesView

Static Suview As NotesView

If Suview Is Nothing Then

Set Suview = UsefulDatabase().GetView("Useful")

' add error handling/generation as appropriate

End If

Set GetUsefulView = Suview

End Function

6) Blog bug?
Wayne Sobers | 9/21/2007 4:39:21 PM

shouldn't the line

"Set m_paddockView Database.GetView(PADDOCK_VIEW_NAME) "

have an equal sign in there, like ...

"Set m_paddockView = Database.GetView(PADDOCK_VIEW_NAME) "?

7) re: Blog bug?
Andre Guirard | 9/21/2007 6:37:28 PM

Hm. It looks different in the preview. Better now?

 Add a Comment
Comment:  (No HTML - Links will be converted if prefixed http://)
Remember Me?     Cancel

Search this blog 


    About IBM Privacy Contact