The Basics of Programming Model Design
Every component developer has to design a programming model. When you write a Component Object Model (COM) control or dynamic-link library (DLL), you must decide how that component will be programmable or, in other words, how developers will write code to manipulate that component.
This usually opens up a whole set of questions: When should I use a property and when should I use a method? How should I name my properties and methods? How should I use and name enumerations? When should I raise events? Developers often can't find any answers to these questions and are left to figure out rules by examining existing published programming models. This can be quite hazardous, especially when the designers of those existing models were not operating by any logical rules either. This article presents some basic rules that I've learned by designing a number of programming models for Microsoft products. Any theory presented here was arrived at following lots of trial and error (sometimes more error than success!) and I have found that by following these rules, one can create usable, understandable and powerful programming models.
You may be confused by the term programming model, but it's really just the correct term for what most people call object model. By programming model I mean the set of interfaces, properties, methods, and events exposed from a component that allows a developer to write programs to manipulate it.
Designing a good programming model is just as important as designing a good user interface (UI) and, not so coincidentally, many of the principles used in UI design can be directly applied to programming model design. Good UI design attempts to let the user work at a much higher level of abstraction than the internal implementation. The UI presents a logical view of the functionality as opposed to the physical reality of that functionality. It expresses things in a way that matches how the intended user thinks—not necessarily how the system actually works.
In the same way, a good programming model doesn't just expose internal structures—it exposes its functionality at a higher level of abstraction so that the customer (the developer) can concentrate on what he or she wants to do and not on how to accomplish a simple task.
What Is a "Programming Model"?
Before I discuss the basic rules of designing a good programming model, I first have to stop and explain what I mean by the term "programming model." Most Microsoft materials (documentation, articles, conference talks, and so on) use the term "object models" to refer to the set of interfaces, properties, methods, and events that are exposed from a component such as a COM control or DLL. However this is actually a misuse of the term.
This distinction in terminology may seem insignificant at first, but I have found that "programming model" is a much better term for describing what we are attempting to design. We must be fundamentally concerned about how other developers will construct entire programs using our components. It's not about exposing the objects that we use internally in the component—these are often way too granular and don't provide the developer (our customer) with a high-enough level of abstraction, which will ultimately make our customer's job easier. I'll discuss this difference between internal objects and higher-level abstractions in more detail below in the "Design Using Scenarios" section.
Why Is This Important?
Designing a good programming model is just as important as designing a good user interface (UI) and, not so coincidentally, many of the principles used in UI design can be directly applied to programming model design. Good UI design seeks to let the user work at a much higher level of abstraction than the internal implementation. That's why we can drag and drop icons into folders to manage files, and the user doesn't need to know what mechanism is being used to track it all. The UI presents a logical view of the functionality as opposed to the physical reality of that functionality. It expresses things in a way that matches how the intended user thinks and not necessarily how the system actually works. The UI, in combination with how it reacts to user interaction, is often described as the "user model."
In the same way, a good programming model expresses its functionality in a way that matches how the developer wants to think about it. A good programming model doesn't just expose internal structures—it exposes its functionality at a higher level of abstraction so that the customer (the developer) can concentrate on what the customer wants to do and not on how one has to accomplish a simple task. For a concrete example of this, see the section below entitled "Design Using Scenarios."
Basic Rules That Lead to Better Models
The following sections concentrate on some basic rules I've learned in the process of designing a number of programming models for Microsoft products. Any theory presented here was arrived at by lots of trial and error (sometimes more error than success!) and I have found that by following these rules, one can create usable, understandable, and powerful programming models. Where possible, I'll note instances from real programming models that illustrate the concept.
Note that these rules are just guidelines. It's perfectly appropriate to break the rules when it makes sense to do so. The topics below should give you some understanding of the basic principles at work in designing programming models, so they will also help you to know when it's appropriate to break those rules.
The most important rule of all is to be consistent. Being consistent happens at two different levels. First, you must be consistent within your own library—nothing frustrates developers more than having something work one way in one part of your library and a different way in another part. Second, you should be consistent with other programming models with which your customers will likely be working. Note that I said "should"—sometimes it's difficult because there are few generally followed conventions and sometimes other libraries completely contradict one another.
For any design decision you have to make regarding your programming model, there is probably a wealth of precedent established in the component community (those writing COM controls and DLLs). However, not all those precedents are good or make sense, so don't blindly follow a convention established in your favorite library unless you determine that it meets your customer's needs. Consistent stupidity is still stupidity.
Design Using Scenarios
If consistency is the most important rule, designing your programming models around scenarios is a very close second. This is typically the most often overlooked rule.
"Scenarios" is a word we use quite a bit at Microsoft. It's a fancy word that simply means "things real people will typically do with your stuff." When we design UI, we think about what people will commonly do in the UI and we strive to make those things easy and automatic.
In the same way, our programming models need to be designed with common scenarios in mind. What will developers typically want to do with your component? Which features are more for advanced users and which features should be very easy to use? Note that the focus here is on the features and not on the internal objects, data structures, or functions.
For any new programming model, first decide what the top 5 to 15 programming scenarios are and document them. Then, after you've thought through the basic structure of the model, but before you've done any detailed work, write the code for those scenarios. Write the code that makes the most sense for the scenario—in other words, write the simplest, most logical code segment that you, as a developer using the component, would expect to have to write. You will quickly realize where shortcuts need to be made and where your overall structure is too complicated.
For example, suppose you were designing the programming model for a component that exposed the functionality of the file transfer protocol (FTP). The implementation of this component would have to deal with hosts, sockets, headers, streams, directories, files, and so on. But that's not what developers using this component want to think about (that's why they want to use a component and not develop it themselves). They want to do things like (in order of priority):
- Copy a file from a host computer to a client.
- Copy a file from the client back to the host.
- Enumerate files and directories on the host.
The first two tasks are by far the most important, and should be incredibly simple to code. For example, to copy a file form a host to a client, I would expect to have to write code like this:
'assuming the control or object is created and assigned to "MyFTP" MyFTP.Open "ftp.microsoft.com" MyFTP.CopyFile "foo/bar/somefile.zip", "c:\temp\somefile.zip" MyFTP.Close
This is only three lines of code, but it could be even simpler. For example, the connect/disconnect methods could be made optional, allowing the host name to be included in the source file name (for example, "ftp://ftp.microsoft.com/foo/bar/somefile.zip"). If they were not used, the component could automatically do connection pooling, providing adequate performance in most common situations. Also, the second parameter of CopyFile could be made optional, where the destination file is automatically named after the original and placed in the current program directory.
Keep in mind that all of that advanced functionality, such as explicit connection management, asynchronous copying with events to notify of progress, and custom stream implementations that let the developer stream the file into a database or a Exchange server folder, can still be enabled. The point is that the developer should not have to deal with these when doing simple, common tasks.
This kind of design uses the principle: "Make the common things easy and make the advanced things possible." It creates a design that works a bit like most VCRs. Just about anyone can push a videotape in and watch a movie. That person does not have to know that the VCR can be programmed with 50 different events, that they can manually adjust the tracking, or that it can freeze the picture and move frame by frame forwards and backwards. These are all features that are there for use, but they are not exposed to the novice user.
When to Use Properties vs. Methods
While the first two rules are more philosophical, the following topics deal with some more mundane rules that have mostly been established through precedence. However, I'm always surprised to see how many people have never really heard them stated definitively before, so even more experienced designers will still find these sections useful, at least for explanations of why things are the way they are.
The first question people will often ask me is, "When should I make something a property and when should I make it a method?" Note that in COM everything is really a method. Properties are always just a pair of get and set methods that have been marked as a property in the Interface Description Language (IDL). For Microsoft® Visual Basic® developers, this is accomplished by using Property Get and Property Let/Set procedures instead of functions and subroutines. When the developer marks something as a property, Automation clients such as Visual Basic and Visual Basic Scripting Edition (VBScript) can then refer to that as if it were a data member of the object, but code is always run in the class. In fact, if you just mark a class-level variable in your Visual Basic class as Public, Visual Basic automatically creates a simple set of property procedures for you.
To determine if something should be a property or method, use the following rules:
- Anything that reflects a state of the object should be exposed as a property. Examples include Caption, Opened, or Visible.
- If the object state is read-only, it should still be exposed as a property but should be read-only (that is, it does not include a propput or, in Visual Basic, a Property Let/Set procedure). Examples might include hWnd, hDC, or WindowStyle.
- If getting a value has no real side effect on the object, it's likely a property and not a method. A property get can involve code that retrieves something from a data source the first time it's requested, but that should be hidden from the developer using the interface.
- Similarly, getting a property's value should not be order-dependent. It should make no difference if you get the value of property A and then B, or vice versa.
- Anything that performs an action and has no real "property get" meaning should be a method. Examples here include Open, Save, Export, Add, and Remove. If the method is directly affecting a state that would be valuable for a developer to also be able to read, it's probably a property and not a method.
This last point is important. I have often designed something as a method first and then later realized that it also would be useful to get the value of the state affected by the method. This sometimes means turning the method into a Read/Write property, and other times just means adding a Read-only property that is affected by the existing method call. The right choice depends on your design style and what you are trying to expose to the developer.
Naming Properties and Methods
There are a few simple but important rules for naming properties and methods:
- Property names should generally be nouns or adjectives (for example, Caption, BackColor, and ConnectionString).
- Method names should generally contain a verb (for example, Save, Connect, Randomize).
- Avoid using negative names, especially for Boolean properties. This results in a double-negative statement in code when the developer sets the property to False, which is very hard to read and understand at a later time (for example, MyObject.PreventRecalc = False).
This one I learned the hard way. In RDO (Remote Data Objects) 2.0, we introduced the ability to create the rdoConnection object independently. That meant we needed to have a method exposed to connect to the data source. However, we already had defined a property in RDO 1.0 called "Connect" which held the ODBC connection string. "Connect" is a verb and would have been perfect as a name for the method used to connect, but we had already used it for a property (something we never should have done in the first place). Developers were constantly confused by this and many thought they opened the connection by setting the Connect property. That's why in ADO we called it "ConnectionString" as this made it a noun and made the purpose of the property much clearer.
Beyond the simple rules above, there are some other general rules that apply to specific types of properties and methods.
Naming Boolean properties
Boolean properties (sometimes called flags) are very common in programming models, and if you look at most models today, there is little consistency between the names of these types of properties.
More importantly, Boolean properties commonly serve one of a few different functions, and your naming of the properties should denote the function the property serves. The different functions I have identified over the years are discussed below. The prefixes I suggest are purely my own opinion and there are many others that would work just as well—just make sure you remain consistent in whatever you choose. Write down your rules for your group so that everyone works from a common base. The point is to realize the difference in the functions these flags perform and to be consistent in classifying the flag into one of these functions and naming it accordingly.
The observant reader will notice that I propose using verbs in some of the prefixes below. These are special cases where I believe the verb usage does not hurt the understandability of the property. You may disagree, but just be consistent.
Flags controlling existence or current state
This first class of Boolean properties reflect the existence of something or the current state of something. Examples of this are Visible, Enabled, State, or HasTitle. The last example is a property from Excel charting where a value of False literally means that the Title object exposed by the Chart does not exist (that is, the Chart's Title property will return Nothing/NULL).
There is a debate going on over whether these types of flags should have a prefix at all. The standard properties in Visual Basic, such as Visible and Enabled, do not have prefixes, but newer languages and models (such as JavaBeans) put forth the standard that they should have an "Is" or "Has" prefix (for example, "IsVisible" and "IsEnabled").
It makes sense to follow Visual Basic's convention if you are developing a component that is to be used in Visual Basic. It will be much easier for developers to figure out how to make your control invisible when there is a "Visible" property than an "IsVisible" property, since all other objects in Visual Basic have a "Visible" property. Therefore, whenever you are designing a property that is standard in Visual Basic, you should follow Visual Basic's naming convention.
However, if you are designing a new property that is not standard to your intended or preferred host, you will have to make a decision yourself. Whatever you do, be consistent.
Flags controlling visibility of UI elements
A second class of Boolean properties consists of those that control visibility of UI widgets like +/- buttons, toolbars, status bars, title bars, and so on. For purely programmatic components, these kinds of flags are never used, but they usually show up throughout COM controls.
I typically use a prefix of "Display" with these types of flags. Examples include "DisplayExpandIndicator," "DisplayStatusBar," or "DisplayToolbar." An alternative prefix you could use is "Show." Again, decide for your group and keep it consistent.
Note that if the UI element being shown or hidden has a programming interface unto itself (for example, if you have a toolbar class/interface in your model), it is better to put a Visible property on that class' interface and make that the mechanism for hiding and showing the UI element. Many times you will not have a separate object for the UI element (such as the expand/collapse indicators) so that is when you create flags to control the element's visibility.
Flags controlling end-user functionality
A third class of Boolean properties consists of those that control the way the user interacts with the UI and what the user is allowed to do in the UI. For purely programmatic components, these kinds of flags are never used, but they usually show up throughout COM controls.
Since these types of flags control what is allowed in the user interface, I typically use a prefix of "Allow." Examples include "AllowExpand," "AllowFiltering," or "AllowDragDrop."
You may think that setting a flag like this to False may make the UI widget that enables the functionality invisible (for example, setting AllowFiltering to False may remove a filter button from the UI), so it's sometimes hard to distinguish between this kind of property and the kind above. A general rule of thumb is to figure out if the property causes the functionality to disappear or just to be disabled. If it just disables the UI element, it belongs in this class and should follow the naming convention for these types of flags.
Flags controlling engine-level functionality
The fourth class of Boolean properties consists of those that control engine-level functionality. By "engine-level," I mean functionality that is accessible through code as well as through UI manipulation. Purely programmatic components will have properties like these, as will COM controls.
Since these types of flags enable or disable engine functionality, I typically use the prefix "Enable" for these. Examples include "EnableUndo," "EnableLogging," or "EnableEvents." The prefix of "enable" can sometimes confuse developers, especially when the component also has UI elements that can be enabled or disabled, so you can alternatively use a different prefix. However, the use of "Enable" for engine level functionality is consistent with the notion of enabling or disabling UI elements, so it can work well in COM controls.
The key to distinguishing these flags from the previous kind is to ask whether this flag controls what code or script is allowed to do. If setting the flag to False prohibits code from doing the action, then it falls into this category. If it only prohibits the UI mechanism, it belongs to the category of flags controlling end-user functionality.
Standard property names established by convention
There are a number of property names that have been established quite firmly by conventional use over the years. This section lists a number of the most common ones, though it is not meant to be exhaustive. If you are considering creating a new property, check to see if the concept exists in other programming models and if there seems to be a canonical name for it already.
Note that controls that will be put in containers (like forms) should not define Right, Left, Width, or Height properties. These are automatically added to your programming interface by the host, as the host controls the placement and size of your window in the form.
|Property Name||Common Meaning|
|Visible||Whether the object is visible (painting on screen) or not.|
|Enabled||Whether the object is enabled. Enabled means it can get focus, disabled means it cannot.|
|Locked||Whether the object is read-only or not. This is commonly used in text boxes so that users can still set focus to the text box and copy the contents to the clipboard or scroll around, but they cannot change the contents in any way.|
|Appearance||Whether the object is painting in 3D or 2D or some other effect. This is commonly typed as an Enumeration.|
|BackColor||OLE_COLOR value for background color.|
|ForeColor||OLE_COLOR value for foreground color.|
|Color||OLE_COLOR value for the color of a particular object (used commonly on Font).|
|Font||Font information to use when painting text. This is often typed as StdFont or IFontDisp, but controls can implement their own Font object if desired.|
|Caption||A read-only (in the UI—may be R/W in code), human readable label on something like a window, label control, or command button/check box/radio button.|
|Text||Textual content, for example in an edit control. This is almost always editable.|
|Name||The unique, not-necessarily human readable string ID for an object. This is usually used in code to look something up in a collection of similar objects and it should uniquely "name" the instance. This property does not normally map to something shown in the UI.|
|BorderStyle||What the object's border style is. Usually an enum of values such as none, single line, double line, and so forth, or Window border styles in the case of a window.|
|RightToLeft||If the control is set to flow right-to-left (for locales that read right-to-left) or left-to-right.|
|Tag||A random variant or string value that the developer can set and read at will. The object should take no notice of this property's value.|
|Key||A unique string value used for collection lookup, often using a Hash table. This is very much like "Name" and usually only one of the two is used.|
|Picture||The picture to use with the control. Often this is a background picture or, for a command button, the image shown instead of text. The type for this is commonly IpictureDisp which can handle bitmaps, metafiles, icons, and possibly GIFs and JPEGs now.|
|Align||Controls that can automatically align to the dimensions of their container have a property like this (such as controls that automatically fill the width or height or entire client area of their container). It's usually typed as an Enumeration.|
|Alignment (HAlignment, VAlignment)||Controls the alignment of content presented within the control. Alignment by itself is usually typed as a 10-value enum (nine points and "default") while HAlignment and VAlignment would have four settings each.|
|DataSource||Very specific to data binding. This names the data source for a data-bound control. The control itself does not save this value in its property bag. It is also required that this be typed as "DataSource" (or IDataSource--they are equivalent).|
|DataMember||Very specific to data binding. This holds the name of the "data member" in the data source to which the control is bound. A data source can have many "members" where each member is a Rowset or Recordset. The control does save this value in its property bag and passes this to the getDataMember method of the DataSource interface. Also, this must be typed as "DataMember" which is a typedef of BSTR.|
|DataField||Very specific to data binding but is only present on simple-bound controls and is usually automatically added by the container's ex-object. This names the field within the data member to which this control is bound.|
|Min||The minimum value for the control, usually used with a control that expresses a range, such as a progress meter, a scroll bar, or a slider.|
|Max||The maximum value for the control. See Min.|
|MaxLength (Max*)||A common prefix for expressing the maximum number of something. MaxRecords is used in ADO to control the number of rows brought down from the server.|
|ScrollBars||An Enumeration-typed value that expresses whether built-in scroll bars should be shown vertically, horizontally, or both.|
|Value||The "value" of the control. Normally this is only present if the control has one specific value it can return, such as a currently selected date for a calendar control. Often this property is marked "bindable" and "defaultbind" in the type library so that it can be a simple data-bound property.|
|HideSelection||Controls whether the current sub-selection is visibly hidden when the control loses focus. Most controls highlight the currently selected contained item and make its background color the selection color. If this property were set to True, then the background color would revert to normal when the control loses focus.|
|Redraw||This Boolean property has been on some older Visual Basic controls and was used to temporarily stop the control from redrawing itself. This allowed the developer to make lots of changes to the UI without the control flickering and then ask the control to fully redraw.|
|Count||The count of items within a container. This is commonly used on collections and it should return the number of items in the collection.|
Standard method names established by convention
This section lists a number of the most common and established method names, though it is not meant to be exhaustive. If you are considering creating a new method, check to see if the concept exists in other programming models and if there seems to be a canonical name for it already.
|Method Name||Common Meaning|
|Open||Moves the object from a closed state to an open one. The important thing here is that if you have Open and Close methods, these should give the developer explicit control over the open/close state. When the developer says "close," it should close and free up any resources associated with it being open.
Note that Open is a reserved word in VBA, so Visual Basic developers can't use this method name.
|Close||Moves the object from open to a closed state. See Open for more details.
Note that Close is a reserved word in VBA, so Visual Basic developers can't use this method name.
|Save||Saves the object to permanent storage (file, database, directory, mail store, and so on).|
|Load||Initializes the object with data from permanent storage.|
|Add||Adds something to a container, usually a collection. This will often have the side effect of creating the thing to be added. For an example of this method, see the Windows Common Controls library.|
|Insert||Inserts an existing thing into a container, usually a collection.|
|Remove||Removes an item from a container, usually a collection, but does not usually destroy it. Most often this method passes back a reference to the removed item so it can be inserted somewhere else.|
|Delete||Removes an item from a container and destroys (zombies) it. This may not be needed as the developer can simply remove the item and let all references fall away. However, it's sometimes necessary to get around circular reference problems or just to allow the developer to zombie an item right away, regardless of what might still have a reference to it.|
|AddItem (or Add*)||Adds an item to a control, such as a list box. This is used when you're adding scalar values or don't want to represent the contained things with an object.|
|RemoveItem (or Remove*)||Removes an item added with the AddItem method.|
|Clear||Clears the container of items it's currently containing. Is also used with controls that hold many values (like a spreadsheet, chart or list) and means "get rid of everything and reset as if you just were created."|
|Refresh||Refreshes the control, optionally involving re-fetching data from the original data source if the control is data-bound.|
Parameter Names, Types, and Defaults
When designing methods on your programming interfaces, you must also consider the names, data types, and defaults for parameters to those methods. Again, there are a few basic rules that you should follow when designing the parameters of your methods:
- Although C/C++ developers may be tempted to do so, you should generally avoid using Hungarian notation (where parameter names are prefixed with a set of characters denoting the data type or function of the parameter) in your parameter names. Hungarian notation is not commonly used in existing components and Visual Basic developers will find them especially bewildering.
- The parameter name should describe what information is actually needed so the developer does not have to look in the Help file to figure out what to pass.
- Parameter names should be in title case with no spaces (for example, ConnectionString).
- Parameters should be hard-typed whenever possible and parameters should only be typed as a variant if the parameter does indeed accept multiple different data types. This leads to fewer bugs in your customer's code and makes it much clearer as to what the parameter actually requires.
- Use enumerations if the parameter can take a fixed set of possible values. Code completion editors such as Visual Basic for Applications (VBA), and soon Microsoft ® Visual C++® and script editors, give the developer a drop-down list of valid choices with which to complete a line of code. This allows your customers to introduce fewer bugs and helps them to continue writing code instead of constantly looking at your Help file.
- If the parameter is a Boolean flag, declare it as such and not as Long or DWORD. Code completion editors provide a drop-down list of True and False for Boolean properties, which can help developers avoid mistakes. For example, True in Automation is actually -1; some developers might expect a True value to be 1 if the parameter is typed as a Long.
- Parameters should be marked as optional if they are really not needed, if there's a sensible default, or if they can be provided via property settings as well. This helps your customers see what is required for the basic scenarios versus what is possible with a little more coding. For Visual Basic developers, this is accomplished by putting "Optional" just before the parameter name. For Visual C++ developers, this is accomplished by using the "optional" parameter attribute in your IDL file.
- All optional parameters must have default values. For hard-typed optional parameters, the default value should be put in the type library. For Visual Basic developers, this is accomplished by simply adding
<param> = <default value>on the method declaration line. For variant-typed parameters, you can still include the default value in the type library, but you also have the option of resolving the default value in your code. If you choose this option, Visual Basic developers can use the IsEmpty function to test if a value for the parameter was supplied.
- All optional parameters should be to the right of all required parameters even though this is not explicitly required by Automation. This is more of a personal bias of mine, but it tends to produce cleaner-looking code in your customer's program and also tends to make your programming model easier to understand. (Required things come first, and then if you're interested, you can also provide optional things.)
- Within that ordering, parameters should be ordered in the logical order that a developer would think to supply them (remember this is just like designing UI—the customer is the developer and you should make it as easy as possible to call the method). For optional parameters, this usually means that the most common options used should be to the left of less commonly used options. Put required parameters in a logical order from most basic and at-hand information to the more complex or less available information.
- Avoid using literal or "magic" numbers for default values or values with special meaning. Instead define constants or an enumeration. Common examples of "magic" numbers include -1 and 0. For example, when we were designing the AbsolutePosition property in ActiveX® Data Objects (ADO), we defined an enumeration for the "unknown" and BOF/EOF values. Instead of testing for some magic number like -1, the developer could write code like
If rs.AbsolutePosition = adPosUnknown, which is much clearer and easier to read and maintain.
Following these rules when designing your parameters will lead to a better programming model that will make your customers' lives easier.
Enumeration and Enumeration Members
As a programming model designer, enumerations are one of your best weapons against chaos and confusion. In general, define an enumeration anytime you have a property or parameter that takes one of a fixed set of possible values. Then mark the data type of that parameter or property as the enumeration. In Visual Basic, this is accomplished by code such as:
Public Enum MyEnum Value1 = 1 Value2 = 2 Value3 = 3 End Enum Public Property Get MyProperty() As MyEnum … End Property
Now when you use MyProperty in code, a drop-down list containing the values in your enumeration appears.
This gives your customers a quick listing of the possible values for the property or parameter and helps them avoid errors due to bad typing or misunderstanding what values are acceptable.
Note that Automation clients such as VBA and VBScript do not actually do run-time checking to make sure that the value the client passes is in the enumeration. This is bad and good. It's bad because you don't get the really strong type checking that you might want, but it's also good because you can let developers pass other values that might be valid and use the enumeration for the values with special meaning. The AbsolutePosition property is an example of a property that uses this technique.
Naming enumerations and enumeration members
There are two types of names to worry about with enumerations: the name of the enumeration itself and the names of the various members within a particular enumeration. The naming conventions you use for these two categories is up to you, but following is a list of rules that I typically use and that many Microsoft components also follow. Again, the most important thing is to be consistent first within your library, and then with other libraries that your customer will likely use along with yours.
- Names of enumerations should end in "Enum" and the beginning should be reflective of what they apply to. For example, in ADO we defined a "CursorTypeEnum" and a "CommandTypeEnum." The first is used whenever you specify a cursor type (CursorType parameter or property) and the second whenever you specify a command type.
- Enumeration members should have a two- or three- character prefix and optionally a grouping word after that. Specifically, this means:
- If the enumeration is used by more than one component in the library (for example, multiple controls or logical groups of classes and interfaces), the members should have a prefix that is an abbreviation of the library name.
- If the enumeration is used by only one component, the members will have a prefix that is an abbreviation of the component to which they belong.
- The grouping word is something that will help your customer recognize the association of a particular member out of context. For example, all the prompting enumeration members in ADO are called adPrompt<rest of member>. To see adAlways in code somewhere would make it hard to know what "always" was referring to, but adPromptAlways makes it very clear.
For an example of how enumeration prefixes work between shared enumerations and those that are specific to one control within a library, see the Microsoft Windows Common Controls library (comctl32.dll).
There are essentially two ways to solve this problem (three if you count the option of just not caring). The first option is to write and distribute to scripting clients an include file that defines global variables for all your enumerations and constants. This is similar to what component developers had to do in Visual Basic version 3.0. It's not a great solution because it creates a new file that you must distribute and explain, and it can be very time-consuming to keep the file synchronized with your component's code.
The other option is to add something to your programming model that exposes constants to scripting clients. In some of the new programming models we are defining in Microsoft Office, we have included a Constants property on the main programming interface. This property points to another interface that contains a set of properties, one for each constant or enumeration member. For memory consumption and other reasons, we define this as a pure dispatch interface (using only late binding).
Events are a very powerful mechanism and many programming model designers will shy away from them or expose very few events for the first release of their component. Events definitely introduce much more complexity than properties and methods do, both for the programming model designer and for the developer creating the component.
In general, events are used to notify the client that something interesting has happened and give that client a chance to run some code. COM controls obviously use events quite frequently, because they need to let the developer know when the control has been clicked, double-clicked, or manipulated in some way. But purely programmatic components can also raise events at interesting times and enable the creation of very powerful programs that use them.
Defining reentrancy behavior
One of the trickiest things you must do when designing events is to define the reentrancy behavior of your component. This means you must define what happens when a developer writes code that manipulates your component during an event your code raised.
You might be thinking "I don't need to worry about reentrancy—I'm not writing multithreaded code." However, by reentrancy here I mean code reentering your component in ways you might not expect. Imagine a control that fires a Change event, and during that Change event, the developer writes code that changes the content of the control again. Should the Change event fire again?
Your support for reentrancy can range from nothing being allowed to most anything being allowed. However, you will often find yourself needing to define something in the middle, and this is where clear definition is needed to avoid confusion and bug reports from your customers.
There are two important things to define with respect to events and reentrancy. The first is if your events will be raised both in response to UI manipulation and in response to programmatic manipulation. For purely programmatic components, this must obviously be answered as the latter, but for COM controls it's an important question. The answer chosen by existing controls available on the market is definitely not consistent, even within the control itself. For example, if you write a calendar control that raises a SelDateChange event when the currently selected date changes, should the event fire both when I select a new date in the UI and when I write code that changes the currently selected date? Or should it only fire when changed through the UI?
The answer to this question will determine the structure of your customer's code. Developers commonly want to keep similar code in one place, so they would commonly like to handle the case when the date changes in one function. They will most likely begin by putting code in your SelDateChange event. Now if they happen to change the date in code, they would probably like to have the event fire so it would be handled appropriately. But what happens if they need to change the date, but don't want their handler code to run (say, when they first initialize the form)? They now have to use a module variable to ignore the event. This may be an acceptable thing because it may be much more common that developers will want the event to always fire than not. The specific answer depends on your component and the scenarios (there's that rule again) for which you are designing.
The second thing to define is what should happen if a developer writes code to modify your component during an event that was raised from your component. This is especially important when the developer modifies the component in such a way that the same event would naturally fire again, creating a never-ending loop that will eventually overflow the stack and crash your customer's program.
Typically, components will freeze other or further events from being fired while raising an event. Component developers use a simple private class-level flag that is set to On just before raising an event (using RaiseEvent in Visual Basic). Before setting the flag to On, the developer first checks to see if it's already on, and if so, does not raise the event. When the original event returns (the line after RaiseEvent in Visual Basic), the developer sets the flag to Off, and events will be allowed again.
If you decide to allow the further raising of events during an event, you must be very careful to avoid recursive loops innocently caused by your customer. Remember that they won't care about the intricacies of software engineering when they can't figure out your component and it keeps crashing. They will more likely throw it away and get another similar component from a different vendor.
There is a raging debate going on as to how events should be named.
On one side of the fence are those that think after events (events that are fired after something has happened) should be named with an "On" prefix, such as "OnClick," "OnKeyDown," and "OnLoad." This is how all events are named in the DHTML programming model as well as in JavaBeans.
However, the official COM standard differs, advocating no prefix for after events (for example "Click," "KeyDown," and "Load"). This standard is followed by Visual Basic as well, in forms and in all components shipped with Visual Basic.
So, if your component is designed to primarily run in Visual Basic, you should follow suit. Again, it will make it easier on your customer if you are consistent with other programming models the customer commonly uses. If you are designing your control to primarily run in Microsoft Internet Explorer, you will most likely want to follow the DHTML convention, unless your target customers will be more familiar with Visual Basic or will be using other COM controls that don't use prefixes for after events.
There are a few other classes of events that designers typically use. These are commonly called before events, during events and overriding events.
"Before" events are those that are raised before an action will take place, and they commonly provide a mechanism for letting the listener cancel the operation or make last-minute adjustments to settings. Before events should have a "Before" prefix as in "BeforeConnect" or "BeforeExecute." To let the developer cancel the action, designers typically put a Cancel parameter as the last parameter in the event that is passed by reference. The parameter is initially False, but if the developer sets it to True during the event, the action is cancelled and an error is raised (exception thrown in some systems) back to the code that called the method that triggered the event.
"During" events are those that fire while an asynchronous action is happening but before it finishes. They are commonly used to let the client update UI elements such as status bars or animations that let the user know things are still progressing. During events should use a name ending in "ing" if possible, such as "SelectionChanging" or "CommandExecuting." Sometimes designers treat these like after events and call them something like "ProgressUpdate." Both designs are equally usable, but I find the "ing" ending makes it more obvious as to why and when the event is firing.
"Overriding" events are those that fire just before an action will take place. Instead of just allowing the operation to be cancelled, these events allow for a complete override of the action. A success or failure code is returned that lets the component know if it should raise an error from the method that caused the event to fire. Overriding events are uncommon and should only be used in certain situations. The standard naming for these kinds of events is to prefix it with "Will." One example of this kind of event is the WillUpdateRows in RDO 2.0.
Standard Event Names
This section lists a number of the most common and established event names, though it is not meant to be exhaustive. If you are considering creating a new event, check to see if the concept exists in other programming models and if there seems to be a canonical name for it already.
|Event Name||Common Meaning|
|Click||The control was clicked.|
|DblClick||The control was double-clicked.|
|MouseMove||The mouse was moved within the control.|
|MouseUp||A mouse button was released within the control.|
|MouseDown||A mouse button was pressed within the control.|
|GotFocus (onFocus)||Focus was obtained by the control. IE calls this event as "onFocus" while VB calls it as "GotFocus."|
|LostFocus (onBlur)||Focus left the control. IE calls this event "onBlur" while VB calls it "LostFocus."|
|Initialize||The control is being initialized. This is usually fired to let developers set initial properties of a control.|
|SelectionChange||Selection within the control has changed.|
|Change||The contents of the control have changed.|
|BeforeChange||The contents of the control are about to change.|
|Scroll||The contents of the control were scrolled.|
|Validate||The listener should validate the contents of the control.|
|Error||An asynchronous error occurred.|
Document Your Component
Finally, component development requires good documentation. In fact, I would argue that it requires much more documentation than an end-user product. If your customer is a developer seeking to integrate your component into another application, he or she will need to understand your programming model.
Microsoft products typically include embedded help, such as ToolTips, status bars, and so on, in the application, to provide quick help that may keep the user from having to open the help files and derail his or her intended task. In the same way, you can embed bits of helpful information into your programming model that will assist developers in knowing what a particular property, method, event, or class is for without requiring them to open the help system. It will also serve as a reminder to the more experienced developers. The way you do this is by embedding documentation strings (or "doc strings" as they are commonly called) in your type library. These strings show up in most development tools in the object browsers and sometimes right in the code editors themselves.
For C++ developers, this simply involves including a helpstring attribute for each property, method, event, and interface in your type library, using the following syntax:
helpstring("This method does . . .…")
For Visual Basic developers, this string can be set in the Procedure Attributes dialog box available from the Tools menu. Type your help string in the Description text box. This dialog box is rather clumsy, so if you are trying to set a number of these all at once, you may find it easier to open the source files in a text editor and add the line that is highlighted in the example below:
Public Sub MyMethod() Attribute MyMethod.VB_Description = "The MyMethod does ..."
Visual Basic persists the description from the Procedure Attributes dialog box in this manner, so if you insert the string manually via a text editor, it will load correctly when you reopen your project in Visual Basic.
These documentation strings may not seem relevant to you, but any developer using your component will thank you many times over if you include meaningful and accurate descriptions of your programming interface as documentation strings. When you are designing your programming models, write these strings down in your specifications as you design the property, method, event, or interface and make sure they are included during development.
When to Break the Rules
Now that I have listed some of my rules for designing better programming models, I will share with you the most important rule: Don't blindly follow rules you read somewhere without verifying that it makes sense for your situation!
The only rule from above that I would argue must be followed at all times is "be consistent." Designing for scenarios and embedding documentation strings are also rules that should be followed unless there's a very, very good reason not to do so. The other rules are just helpful advice that I have collected over the years and have found works well. At their core, they contribute to the overall goal of designing with your customer's needs in mind and not your own. The details (such as what prefix to use when naming certain types of Boolean properties) are open for modification. Just make sure you remain consistent.
For every detailed rule above, you can probably find one or more libraries that have done things differently. Sometimes you may find that it would be better to be consistent with a not-so-good design decision made in another library than to be inconsistent with it. However, be wary of just copying design patterns from other programming models as they may be quite poor designs, and you're not helping anyone when you do that.
There are plenty of bad examples out there, but if you keep the principles I've outlined in this paper and your customer's needs in mind when designing, you will likely start to see the bad design choices in your favorite programming models and keep yourself from making the same mistake in your own programming models.