PHIL RIDLEY
Phil has been working with Visual Basic since version 3, mainly within the finance and insurance areas. His main area of expertise is creating large, reusable business objects, using base code wherever possible. When not working seven days a week, Phil can be found watching football or playing the occasional round of golf.
Do engineers reinvent the wheel every time they need to build a new piece of machinery? Of course not. So why do we, as developers, almost always do the equivalent when building new applications? C++ developers have been using MFC for years. J++ developers have AFC. So why don't we have our own equivalents? At The Mandelbrot Set (International) Limited (TMS), we've been using our own application template for a number of years for all new projects (see Appendix for more details).
In this chapter, we'll look at some of the reasons for using base code and some of the techniques that can be applied to creating your own base code. Finally we'll look at how Microsoft Visual Basic templates can be used to provide frameworks for common forms, classes, and modules.
How many times have you sat down to begin a new project and wondered exactly where to start? Do you begin by creating the screens you need, writing the low-level functions, or (as often seems to happen) do you just leap in somewhere in the middle? Then maybe somewhere down the line, you suddenly realize that you need to decide how to handle and report errors. This can leave you having to retrofit an error handling strategy into hundreds, if not thousands, of lines of code. With a properly designed set of base code, most of these decisions have already been made, and vital items-such as a coherent error handling strategy-are already implemented. All that remains is for you to plug in the forms and routines that are specific to your individual application.
Any serious application-regardless of size-will need to have some form of infrastructure. By infrastructure, I mean units of code that bind the application's functional units and provide general application support services. A classic example is the error handler whose job it is to maintain application state under erroneous conditions, and provide support services such as logging and tracing. Infrastructure functionality is essential to the application, but often the functionality is not included in any functional or design documentation. This can create problems: now the programmer has to write infrastructure code that has not been factored into the development time estimates. We estimate that using our own application template saves about a month of development time when creating an application from scratch. I'm sure most developers will agree that the majority of projects are developed under stressful time constraints, and an additional month of unplanned activity can mean less sleep! Multiply that by the number of applications your company develops during a year and you can start to see the cost- and time-saving potential of using base code templates.
Timescales are more often than not the major driving force behind an application. However, when the crunch comes, the timescale often succumbs to the more urgent need to iron out bugs and implement overlooked functionality. The problem with not having developed base code up front will manifest itself as a continual series of retrofit changes, each one becoming more difficult as the amount of code to integrate grows. In the reactive rush, standards slip and duplication occurs. How many occurrences of the bFileExists function can you find in one of your applications? The problem of duplication will always exist when working under these conditions because programmers on a project rarely have time to share information about functionality that they have added. As a programmer, you will probably assume a function you require does not exist and write it yourself. If that function does happen to exist already, there is a high probability that its code is different, and it might even have a different function name. The contract market is booming, and sometimes a particular programmer has worked with several programming standards from a variety of organizations. In high-pressure situations, the programmer is tempted to treat infrastructure routines as almost incidental. Instead of meticulously sticking to the organization's standards, the programmer might wish to code quickly, using whatever standard comes to mind. Comments will be scarce-if written at all. By this point in the development cycle, all the business can see is slipping deadlines. Code reviews-what are they? Congratulations, you now have most of the ingredients for creating the perfect maintenance nightmare!
By now it should be obvious that using base code has benefits. From a coding perspective, base code allows developers to concentrate on the functionality of the application-that is, the functionality that has been specified and cost-quoted. Most developers would much rather spend their time developing the application functionality because that's where the "clever" algorithms come into play. Writing this code is a real ego boost and many developers take pride in being able to write code that stretches their abilities to the limit. This code is far more likely to contain comments and follow the official coding standards. You can almost hear the programmer crying out for the "work of art" to be reviewed.
Base code encourages code reuse because the average programmer does not want to write bFileExists. Knowing that there is a base framework containing this "trainee programmer" type of code, programmers are more likely to actively seek out such a routine rather than write it themselves. However, you should remember that many programmers have limited patience. If you were writing a really cool routine that had to perform different actions based on the operating system, you might scan for a GetOSPlatform function in the base code. If such a function did exist, it would be vital that the programmer could find it-in a reasonable amount of time. Intuitively named modules might help here. Another major help is the function's interface. I will cover these topics in more detail later in the chapter.
An application's base code will often need to use specialized API calls, especially for the environmental-type functionality. One example is the Version API functions. In the old days, a badly defined or called API routine would simply cause a General Protection Fault. However, Microsoft got fed up with complaints about such faults-invariably caused by misuse-and built extra layers of validation into the API. Now you can get away with both wrongly declared and wrongly implemented API calls. In most cases the routine just doesn't work but the application is not as likely to crash. Using a good base code template allows complex functionality like this to be abstracted from the application. The developer need not understand how a function works, just that it does. A good base code template can allow developers new to Visual Basic to come to grips with applications more quickly than they otherwise would by concentrating on the business functionality rath 444o1415e er than the infrastructure. There are many other reasons you might want to seriously consider using base code templates. Here are some more:
Using a base code template means that tried and tested, complex, low-level routines can be added to projects in the knowledge that they already work. Many of the routines in the TMS Application Template contain complex API calls, which, if the routines were written from scratch, would require extensive debugging and testing. When you are writing an application, the knowledge that some of the messier functions have already been written and debugged can be a great relief.
Starting from a set of base code imposes a set of coding and design standards on the developer right from the start. When an error handling strategy has already been implemented, it is usually much easier to go along with what's already there than to branch out in a different direction. Similarly, when developers become familiar with the naming conventions used throughout the base code, they will be more likely to fall into line with them than to ignore them.
Base code allows you to implement a consistent subclassing strategy. Subclassing is an area that I will be discussing in more detail shortly. For those not familiar with the term, subclassing is basically the concept of taking an existing Visual Basic function or object and replacing it with one of your own so that it is modified functionally.
I hope that by now you have been sold on the idea of base code templates. On all levels, from corporate to project management to development, base code templates are useful. The bottom line, I suppose, is that using some form of template will save you money, time, and effort.
The first step in creating a base template is deciding what to include. The purpose of an application template is to provide the core services that an application is likely to require, but that do not in themselves warrant inclusion in the main application design documentation. The code that makes up the template will in most cases be relevant to many types of application. Some routines will not be needed right from the start, but it is wise to include any routines that might be of use as the project progresses. This section offers a general guide.
A helper function-such as bFileExists-is an obvious start for your template's library, but so too are the less obvious ones such as ExtractFileName, ExtractPathName, and AppendSlash. At first you might find it hard to think of helper functions to write. You should be able to find a good selection by looking through existing applications. I'm sure you will find many more candidates this way. Looking through existing applications can also help you to identify code that is duplicated between applications and therefore might warrant inclusion. Another way of identifying code for your template is to use a code analyzer on a few of your existing applications. This will help you to identify the routines that are receiving the greatest number of hits, and therefore may prove more useful in template code where you can more easily optimize them.
Common forms can be a real asset to an application. The Common Dialog control is a classic example of a common form. A progress form, About Box, and Message Box are just some of the common forms you can create. Common forms will give your applications a more standard appearance, and again you will not end up with many implementations of the same functionality. A common application MDI form should not be overlooked either. Having such a form will allow you to pre-create the standard menus, such as File, Edit, Window, and Help. Again, this is the type of code that is a bit of a chore to write for each different application.
Creating Microsoft ActiveX components can be a powerful way of enforcing business rules. Many applications within an organization will need to access a common set of business rules, creating a uniform look and feel. For example, you could create an ActiveX control that wraps your favorite grid control, and build additional functionality such as allowing the user to cut and paste rows of data. Centralizing these rules into an ActiveX server makes sense. That ActiveX server can then be considered base code.
Subclassing objects and procedures is a powerful technique you can build into your template to change the way that standard Visual Basic elements work. You could, for example, subclass Visual Basic's Load method. "Why would I want to do this?" you ask. Well, imagine an application with a complex-state engine that must control what forms can be loaded depending on application state (not an uncommon requirement). By subclassing the Load method, you will effectively channel every load request via your function. You can then determine what is being loaded and either prevent the load from occurring, or permit it. In effect, all the state code is in a single place. Programmers need not worry if it is safe to load a particular object-they just make the call as normal and the subclassed method takes care of the rest. Couple this into your menu control logic and you have the potential to control your application state from a single point! You can subclass many other Visual Basic elements. I will explain how to do this in more detail later in the chapter.
Now you should have a few ideas to start with. Remember that building an application template is a task that should be treated as a project in its own right. Time and budget will need to be allocated for the task; therefore, it makes sense to pin down the requirements beforehand. The success of your template will very much depend on the thought that has gone into its design. In the following sections, I will explain how to perform some of the tasks we have described so far.
To fully understand how subclassing works, you have to understand how Visual Basic resolves which version of a function or subroutine to execute. When Visual Basic encounters a statement such as Call MySub, it begins a search for the MySub definition. First it looks within the current module, form, or class module for a definition of MySub. If it fails to find one, it looks through the rest of the project for a public (or friend) definition of MySub. Failing that, it goes to the list of project references (available by choosing References from the Project menu) and resolves each reference in order until it finds the first reference that contains a public definition of MySub.
If you check the project references for any project, the first three items in the list you will see are Visual Basic For Applications, Visual Basic Runtime Objects And Procedures, and Visual Basic Objects And Procedures. Because these references contain the definitions for all the intrinsic Visual Basic subroutines, functions, and objects, you will see that, following the rules of the previous paragraph, it is theoretically possible to define local versions of any intrinsic Visual Basic subroutine, function, or object within the program. Unfortunately, this is not the case, as there are some functions that cannot be subclassed for various reasons that I will discuss later in the chapter.
Subclassing allows you to extend, expand, and even diminish, or degrade, the existing Visual Basic functions and subroutines-perhaps to add extra validation of parameters, perform additional processing to the original function, or even to change the purpose of the function or subroutine altogether. This last choice has obvious pitfalls for users who are not aware that a routine now performs a completely different function than what the manual says, and should be avoided for that reason. For the first two purposes, however, subclassing can add real value to the existing Visual Basic equivalents.
One useful application of subclassing that we have implemented is in replacing the standard Visual Basic MsgBox function. The code for our MsgBox function is shown here:
Public Function MsgBox(ByVal isText As String, _This is an example of a routine where we have completely replaced the original Visual Basic functionality. However, it is also possible to use the existing functionality and extend it, as in the following example:
Here we are simply taking the EXEName property of the App object and reformatting it to our own standard. Note that to access the original Visual Basic property we must fully qualify the reference. When this is done, Visual Basic ignores the rules above for resolving the location of the routine and instead resolves it directly using the explicit reference.
The potential for subclassing Visual Basic intrinsic functionality should not be underestimated. In the MsgBox example alone, the scope you have for customization is enormous. For example, the TMS MsgBox function allows you to log any message displayed with the vbCritical flag. It contains auto-reply functionality in which you can have the message box automatically choose a reply after a given period of time. You can also configure up to four buttons with custom captions. All this functionality from one Visual Basic method!
Some Visual Basic functions are not suitable for subclassing. Some functions simply won't subclass because the function names have been made reserved words. Print is such an example, as were CDate and CVDate in Visual Basic 5. Ultimately, there is little that can be done about such functions except create functions with similar-but different-names. This requires that the developers be instructed to use the renamed function instead of the intrinsic function, and thus leaves the door open to inconsistencies within the code.
Functions that have multiple definitions but return a different result type (for example, Format and Format$) are also not possible to subclass fully. The problem here is that two functions are needed to subclass the function properly, but Visual Basic sees Public Function Format(.) As Variant and Public Function Format$(.) As String as being the same function and raises a compile-time error of a duplicate function definition. Here, the only option is to subclass the more generic function (in this example, the more generic function is Format, as it returns a Variant) and issue instructions to developers not to use Format$.
Other functions break Visual Basic's own rules on parameters. An example of this is InStr, which takes four arguments, of which the first and fourth arguments are optional. As Visual Basic's rules state that optional arguments must be at the end of a list of arguments, clearly we cannot subclass this function to match exactly the internal definition of it. To get around this, we either have four arguments, with the last two optional, or simply use a ParamArray of Variants as a single argument. Both of these solutions require more work within the new version of the function in order to work out which of the four arguments have been supplied.
One "nice to have" feature would be to wrap the subclassed functions in an ActiveX DLL such that the source code was "hidden" from the developers. Unfortunately, this currently isn't possible in Visual Basic. In the beginning of this section, I described the method Visual Basic uses to determine where to find the definition of a function. Remember the three references I listed that always appear at the top of the project references list? Unfortunately, there is no way to prevent those three references from being at the top of the list. This means that the reference to your ActiveX server with the subclassed functions is always below them in the list and, as a result, your subclassed functions are never called. It's a pity that this approach isn't possible, as it would not only hide the details of what extra functionality the subclassed functions contain, but it would also remove the clutter of the extra source code from the application.
Just as it is possible to replace the Visual Basic versions of functions and subroutines, it is also possible to replace the "free" objects that are provided for you during program execution. The App, Err, and Debug objects can all be replaced with objects of your own design, although this does require you to instantiate an instance of each object you are replacing.
Because you are creating new objects of your own, it is essential to include all the properties and methods of the original object and simply pass the calls through to the original object where there is no extra functionality required. An example of this is the hInstance property of the App object, shown below.
Public Property Get hInstance() As LongNOTE
Note that hInstance is a read-only property of the VB.App object so you would not code a Property Let for it.
The main uses we have found for subclassing the App object are to add new application-level properties and to reformat the output of some of the standard App object properties. Typical properties that we find useful to add are InDesign (a Boolean value that tells us if we are running within the Visual Basic IDE or as a compiled EXE or ActiveX component), date and numeric formats (from the Registry), the operating system version details, and various file locations (the application help file and MSINFO.EXE, among others). Other uses for subclassing the App object include providing a consistent version number format, and the ability to store Registry settings in different locations depending on the InDesign property above (we append either ".EXE" or ".VBP" to App.ExeName).
This is probably the most difficult and least useful of the system objects to subclass, mainly because an object cannot be created with the name "Debug" and the Print method cannot truly be subclassed, as "Print" is a reserved word. For these reasons, it is probably best to leave the Debug object alone and, if extra debug-like functions are required, create a completely unrelated object.
This is probably the most useful object to subclass, as it enables you to apply more consistently your chosen error handling strategy. We add Push and Pop methods to the error object, and we also make the undocumented Erl function a property of the object. In addition, we add an ErrorStackList method that allows us to examine the current error stack that we maintain internally within the new App object. This allows us to determine, from any point in a program, the routine that raised the current error. For more information on error handling strategies, and an implementation of a replacement "Err," please refer to Chapter 1.
One note of caution with the Err object relates to the Raise method. Although you should probably add a Raise method to your subclassed Err object for completeness, you should add code to it to ensure it is never called. The reason for this becomes obvious when you stop to think about it. Your Err.Raise method would deal with the error by passing the required details to VBA.Err.Raise. This would then generate an error, with the source of the error being your Err object every time! When subclassing the Err object, you must ensure that VBA.Err.Raise statements generate all errors raised in the program.
How many times have you created an "About" form for an application? At the end of the day, all an "About" form does is provide version and copyright information for the application. Copyright information will be standard within an organization, and version information about the program is available via the App object. Therefore, "About" is a great example of a generic form that can query the application for the information it needs, and should require no application-specific code in it.
Want a "Tip of the Day" form? Again, this is a generic form that can get its information from a resource file (for the tips) and the Registry (for the last tip displayed and whether to show on startup).
OK, so you've got your error handling sorted out, but you'd like to make it a bit more intelligent than simply showing a message box with the standard Abort/Retry/Ignore buttons. Some errors are prime candidates for the application to retry periodically but still give the user the ability to abort. All you need is to create a form that looks like the message box form, but you should also add a timer. Then all the form needs is a few properties allowing the buttons to be hidden and shown at will, and a property to tell it to automatically retry the error action every x seconds. Using the Timer control, the error dialog box can click its own "Retry" button whenever the timer fires. Once you have all this code together, there's no reason why this can't be a generic form. Most people already use MsgBox to report errors and that's about as generic as forms come!
Speaking of MsgBox, have you ever needed an extra button, or a combination of buttons that's not permitted, or a button with a completely different name than the standard ones? Simply subclass the MsgBox function and have your own form that mimics the functionality of MsgBox. Now if you need an extra button, you simply need to define a new constant for it and its caption. All the new MsgBox code and forms can then become generic, as they are passed all the information they need from the application when the function is called.
Other useful features we have added to our MsgBox function include automatic logging of critical messages (where the button includes vbCritical) and appending the application title and version details to the beginning of the title text.
Unfortunately, Visual Basic still can't do everything. There are times when we need to get information that only the dreaded Windows API can provide. A common requirement is to get version information about a file. Although we can package up the Visual Basic code into a code module and make this part of the base code, we still need to put all of the API declarations and constants at the top of the module. If we're using base code in such a way that we add only the modules we need, things are further complicated by the fact that we then need to declare the APIs as Private in each module in which they're used. This way we end up with duplicated definitions, potentially declared differently.
This also allows developers to bypass the rule that API parameters should only be declared As Any where this is absolutely necessary. Declaring parameters with a data type of Any bypasses the compiler's type checking and allows any data type to be passed to the API. Where this is necessary-such as with SendMessage, which can take a long integer or a string as its final parameter-it is best to declare the function twice. This allows you to fully specify the parameter type and the two declarations can then be distinguished by being aliased to different names (such as SendMessageLng and SendMessageStr).
The best solution to the problem is to remove the API declarations from the Visual Basic code and put them in a type library. A type library performs the same function as the declarations in code, but instead of adding dozens of lines of code, all we need to add to the project is a reference to the type library. Given that one type library can end up being used by many projects, this makes for a much safer and cleaner way of declaring API functions. Constants needed for the API calls and help text (which will appear in the Object Browser) can also be included in a type library.
Type libraries can be a bit tricky to set up and get right but, once created, simplify your project. (Use OLEVIEW.EXE to "de-compile" a type library back to its source-it's an easy way to learn IDL.) The main difficulty in creating type libraries comes from the fact that the API declarations need to be in C format.
I have included a sample type library on the accompanying CD. The TEMPLATE.ODL file is the source file for the type library and can be opened with Notepad or any text editor. The TEMPLATE.TLB file is the compiled type library from the ODL file and is the file that should be selected in the References dialog box. Once that is done, the APIs that have been declared can be browsed via the Object Browser.
To create a type library you need two tools that ship with the Visual Basic CD:
GUIDGEN.EXE will generate GUIDs for you. (Peet Morris also provides a Visual Basic version of GUIDGEN in Chapter 7.) This is a process that is hidden from you when you create ActiveX components but, put simply, a GUID is the key that is used in the Registry when an ActiveX component is registered. Type libraries require these as well, but whereas Visual Basic generates these and keeps them hidden from you, type libraries require a much more manual process.
MKTYPLIB.EXE is the type library compiler that will turn your ODL source file into the TLB object file.
GetSetting and SaveSetting are valuable tools for getting and saving information to the Registry. However, one of the parameters is the application name, which allows for potential discrepancies between (or even within) applications. To avoid these discrepancies, it is probably best to wrap these functions up in your own functions that hide the application name parameter. This way, you ensure a consistent approach to storing information in the Registry.
Another nice feature that you can implement takes advantage of one of the subclassed properties of the App object I mentioned earlier, App.InDesign. If your GetSetting and SaveSetting functions use App.ExeName and you have subclassed the App object, you can store program information in different locations depending on whether you are running in design mode or as a compiled component.
GetSetting and SaveSetting are good for getting and storing program-related information, but sometimes you might need to retrieve information from other areas of the Registry. Functions for doing this require a number of API calls and can be tricky to create. This immediately makes them prime candidates for being written into a common module with more user-friendly parameters than the APIs.
Another example of how subclassing can be used to extend the language would be to add the ability to access any part of the Registry to Visual Basic's Registry functions.
Resource files are wonderful places for storing strings and graphics that a program might need. Unfortunately, Visual Basic limits us to only one resource file per project, which makes it difficult to use a generic resource file, as we usually require more application-specific data within the resource file.
One way around this limitation is to use a Resource Only Object Server (ROOS). A ROOS is simply an ActiveX component that has the sole purpose of returning data from its own resource file. The ROOS should contain methods to extract data from its internal resource file. You can add all sorts of functionality, such as giving the ROOS the ability to parse tokens and return fully qualified strings. This is similar to how many Microsoft applications work. For example, a ROOS's resource file might contain an entry like this:
IDS_ERROR_BADFILEOPEN "Could not open the file %1, %2"Calling the ROOS method, you could then code:
Dim sFileName As StringIn this example, an error 53 (File not found) would result in the following message: "Could not open the file C:\MYFILE.TXT, File not found." The ROOS code will in fact be base code that never needs to change. It simply provides an access method to the underlying resource file and gives you parsing functionality. You simply need to create the resource file for the project, attach it to the ROOS, and compile a DLL. You can compile each ROOS with a different class name, like ProjectXROOS or ProjectYROOS for whatever project you are using. This gives you the ability to use multiple resource files within any particular project. Chapter 14 gives a detailed example of how you might build a ROOS.
Custom controls are a good example of base code that can be reused time and time again. Tired of having to code a GotFocus event for every text box to highlight its contents? Why not create a custom TextBox control that handles its own GotFocus event but in all other respects acts like a normal text box? The ActiveX Control Interface wizard will generate up to 95 percent of the code you need, leaving you with only a few lines of code to write yourself. A custom control that was generated from this wizard is included with the sample code for this chapter. If you examine the code closely, you will see that the ActiveX Control Interface wizard generated all but four lines!
The sample control is very simple-it simply extends an existing property of another control. However, the wizard makes it equally simple to add custom properties of your own, such as numeric formatting and data validation. For more detailed information on creating custom controls, see Chapter 14. Chapter 6 also provides some information on creating a Year 2000_compliant TextBox control.
A key area with base code that is often overlooked is that of documentation. It's no good having a library of 20 code modules with hundreds of functions in them if developers have to search through all of them each time they want to see if a common routine already exists to do what they want.
Similarly, if the parameters and return values are not documented, developers might not be able to work out how to call a particular routine or how to interpret the results. When this is the case, the developers will invariably end up writing their own versions of the routine. Consider the following infrastructure routine's interface:
GetUserPrivilegeFlags(nUser As Long, _I know that if I were not intimately familiar with this routine I would be tempted to write my own version. For example, is the array one-dimensional or multi-dimensional? Do I need to pass the array with values or an uninitialized array? How on earth are the last two parameters used? You can see the dilemma. The interface is of paramount consideration because it has the potential to scare off any potential users of the routine. Contrast the code above to the same fragment rewritten below:
By using the ByVal and ByRef keywords, it is clear what is expected and what is returned. The argument names have been modified to be more meaningful, and the last parameter, nSpecificPrivId, has been typed so that a drop-down list of values will be displayed to the user, alleviating any ambiguity for this parameter's value. However, we still have the problem of not knowing the format of the exact requirement for nPrivFlags. This problem can be solved by using the Procedure Attributes item in the Tools menu. This allows you to enter a description that appears in the Object Browser when the procedure is selected.
The Object Browser provides a fairly quick way to scan the contents of a code module. To assist with the scanning process, it is helpful to add a short description to the routines. To do this, select Procedure Attributes from the Tools menu. A description can then be entered that will appear at the bottom of the Object Browser window whenever the routine is selected. The drawback with this is that there is only a limited amount of space available here, and complex functions might require more information to be made available; however, it is possible to use the size bar in the Object Browser window to extend the viewable area. For the lazy ones among you, you can in fact enter procedure attributes directly in the code window. You do this by entering the attribute in the procedure-for example:
Attribute GetUserPrivilegeFlags.VB_Description = "My description"The line will be highlighted as a syntax error; however, if you save and reload the file, the Attribute line disappears and the description shows up in the Object Browser. Remember that the success of any base code template will be affected by the level of documentation.
One approach that we use at TMS is to create a developer help file for the base code. This enables a structured view of the base code to be created. We generally create one help topic per code module, which contains the name and a brief description of each function within the module. Any function that requires more information can be linked to its own help topic, which can provide any detailed information to the developer. This approach has the benefit that keywords can be added to all of the topics, allowing for a search of the information by the developer.
Using HTML is a similar approach to using a help file, but this approach has the added benefit of being viewable over the company intranet. It also has the benefit of being maintained centrally; when the base code is changed, the developers will immediately have the updated documentation available.
So far we have discussed why base code is a good idea and some of the techniques that can be used to build it. I hope that by now you're eager to go off and build your own base code. But where do you begin? Who do you get to build it? How do you distribute it to your developers? And most important, how do you put together a convincing business case to justify the project to your boss?
So where do you start when you want to create your own base code? First remember that the gains to be made from base code are in creating new applications and their subsequent maintenance. To attempt to retrofit base code into existing applications would be difficult at best. Are you sure that the routine you're replacing with a base code routine does exactly the same thing?
Second, remember that the beauty of base code is that it is as complete and as thoroughly tested as possible. There's no point in having a set of base code if it's incomplete or full of bugs. It will require a lot of effort to set up initially, but it's worth the effort.
Modules that make up the base code should not have any application-specific code added to them. By its nature, the template must be capable of running as a stand-alone application in its own right. If you design the template independently of any project, you will be creating a set of base code that will work with any application. It is also important that the base code routines are loosely coupled-that is, each routine should be as self-contained as possible. Never write a base code routine that references elements external to the base code modules. Doing so would in effect prevent you from using the base code in any other project without modification. An ideal strategy would be to have the base code stored in a separate Microsoft Visual SourceSafe project, with the individual projects that use it simply linking to the base project for those modules that they need. Any updates to the base modules would need to be coded only once and the change would then be propagated to each application the next time it was built.
The way you actually construct your base code template will depend very much on where you will be using it. For example, code for an ActiveX control will differ somewhat from code in a full-blown application, which will differ again from an ActiveX DLL. Components without a user interface will probably not require items such as a full-blown message box; however, the base code template for such a component might change the message box functionality to raise some form of remote alert. Template code can be written as objects or as standard modules containing functions, methods, and properties. I advise you to build elements as simply as possible. If you do not need instancing, place your code in a standard module rather than in a class module. The key factor to building your template is to loosely couple everything. Doing this will allow you to extract functionality and reuse it for other templates you want to build.
I have already discussed the importance of your interfaces. You also might want to carefully consider your naming conventions. Because you want the interface to be as intuitive as possible, it is a good idea to use names that break the usual naming standards. For example, you might do this when subclassing existing functionality. To stick rigidly to standards might mean changing the names of procedures or parameters that are well established in the language. This is obviously counterproductive, since the whole idea of subclassing is that the programmer doesn't necessarily know that a change has taken place. Of course, the other problem is that by changing the name of the thing you are subclassing, you are no longer subclassing it!
So you're convinced that base code is a good thing and you've decided to build your own. But which of your developers will you get to do it? Are you going to make it a company-wide development or use a small development team to put it together?
While a large team enables you to put the code together fairly quickly, it makes standards harder to maintain. It also leaves the base code without a clearly defined owner, which makes controlling changes to the code more difficult. A small team ensures high standards of code and a clearly defined ownership. Since part of the object of base code is to abstract complex routines from the application, it also makes sense to use a high-caliber team to create the code.
The code that is created is also going to need to be tested to the highest standards. The developers who are going to use the base code rely on its integrity from day one. If they can't, they are likely to code workarounds for any bugs discovered in the base code, which can then cause problems once the base code is corrected.
So do you include the base code in projects as source code or in an ActiveX component? For the reasons discussed above, routines that subclass existing Visual Basic routines must be included as source code in the application. Ideally, as many additional routines as possible should be distributed to the developers in executable form. By placing all the public routines in GlobalMultiUse classes, all the developer has to do is add a reference to the ActiveX component into the project. This also allows the base routines to be subclassed if necessary.
Most companies treat each project in isolation, producing for each a budget and cost-justification. Unfortunately, the project to create a set of base code does not have any immediate impact on the business and is difficult to justify to the business in the short term.
Because the base code must be carefully written and extensively tested before being used in the first project, the business benefits only start to appear about a year after the project starts. Therefore, the project should probably be funded as an internal IT project that can then be recovered from the business in small chunks every time a new application is built on the base code. I cannot stress enough that having no base code will invariably result in either nonstandard code, increased development time, or both. At a time when many organizations are employing the services of third-party contractors, it is important for corporate code to conform to the defined standards of that organization. By having a base to start with, you also increase the likelihood of your standards being adhered to.
Just as it is important to create high-quality base code, it is equally important to ensure that base code is changed in a very controlled manner. The base code project within Visual SourceSafe should be regarded as the highest-security project in your organization. As such, only a select core of developers should have anything but read-only access to the project. Remember that any changes to the code will be incorporated in every other application the next time the applications are built.
Controlling changes to the base code is also extremely important. A strict change control process needs to be in place for the base code and every change request must be fully justified. When a change cannot be fully justified, alternative solutions need to be found. See the sidebar on the following page for one possible scenario (along with a solution).
A nice feature within Visual Basic that was introduced in version 5 is the concept of code and form templates. Now when you add a new form to a project, you no longer get a simple choice between a standard form or an MDI child-you are presented with a whole list of different forms. These are not "common code" in the sense of what we have discussed so far in this chapter, but are close enough to be related.
When you select one of these new types of form, a copy of a prebuilt form is taken and added to your project. In some cases this may simply mean that a few controls have already been placed on the form for you, whereas in other cases there may be significant amounts of code already added. These projects, forms, classes, and modules are all installed in the TEMPLATE folder of your Visual Basic installation.
To create your own templates, simply create the form, module, class, or project that you wish to use as a template, and then save the files in the appropriate subfolder of the TEMPLATE folder. Note that when creating a project template, if you do not want the forms, modules, or classes to be individually selectable, you should save them all in the PROJECTS folder.
What to Do When the Base Code Cannot Be Changed
The base code runs as part of an ActiveX DLL with all the functions in a GlobalMultiUse class. Subroutine Foo is part of the base code. It currently takes one parameter and is called from six applications that use the base code.
One of the six applications needs an extra parameter to be added to Foo. Due to the impact on other applications, this change to the base code is refused. So what can the application do? Simple-subclass Foo! This allows us to retain the existing functionality in the base code but to extend it specifically for this application.
It is possible to subclass our own routines in exactly the same way I described earlier the subclassing of the standard Visual Basic routines.
In this chapter, we've discussed a number of different approaches to using base or template code. You've learned that it isn't necessary to reinvent the wheel every time you sit down in front of the screen. The key to a successful base code strategy is to make sure that it is enforced. There is little point in having a strategy like this if nobody sticks with it. Successful use of base and template code requires a disciplined approach, but can pay huge dividends in the long term.
|