|
March 13th, 2001
Revisions: (1) April 17th, 2001
This article identifies the problems that arise when function interfaces need to be changed and discusses possible ways to tackle these both ex ante and ex post.
Introduction
Modularized code is great. We all know that and therefore write many functions, hopefully ones we can reuse. Sometimes it is necessary to extend the functionality of a function we're using extensively in one or more applications. If it's only the logic that needs to be changed, we can fancy ourselves lucky. The tragedy begins when we determine that a change of the interface of our function is inevitable-e.g. we have to change the number of the function's parameters. Given that VBScript doesn't support optional parameters, such change will break our running applications anytime the function is invoked-until the function calls are adjusted to reflect the changes to the interface.
The Starting Point
The following example illustrates the situation described above. The function html_OneCellTable is used to display a simple one-cell table. The contents and the width of the table are parameterized. Below you see the implementation of the function and a call to it.
- Function implementation and function call
' Function implementation
Function html_OneCellTable(ByVal strBody, ByVal intWidth) Dim strTemp strTemp = "" strTemp = strTemp & "<table" strTemp = strTemp & " width=""" & intWidth & """" strTemp = strTemp & ">" strTemp = strTemp & "<tr><td>" strTemp = strTemp & strBody strTemp = strTemp & "</td></tr>" strTemp = strTemp & "</table>" html_OneCellTable = strTemp End Function ' Function call
Response.Write(html_OneCellTable("Simple table", 300))
The Problem
Let's say we now want to be able to pass the background color of the table as a parameter. We will need to extend the function so that it takes the color of the background as parameter. Here's the updated code:
- Function implementation with additional parameter
' Function implementation with additional parameter
Function html_OneCellTable(ByVal strBody, ByVal intWidth, ByVal strBgColor) Dim strTemp strTemp = "" strTemp = strTemp & "<table" strTemp = strTemp & " width=""" & intWidth & """" strTemp = strTemp & " background=""" & strBgColor & """" strTemp = strTemp & ">" strTemp = strTemp & "<tr><td>" strTemp = strTemp & strBody strTemp = strTemp & "</td></tr>" strTemp = strTemp & "</table>" html_OneCellTable = strTemp End Function
So far so good. Correction-now we're facing the problem that by changing the interface of our function, we break the existing code that used the function. Being unable to declare the strBgColor parameter as optional, we will have to change every instance of code where the function is invoked. After adjustment, we end up with the following:
- Function call. Background is set to blue
' Function call. Set background to blue
Response.Write(html_OneCellTable("Simple table", 300, "blue"))
Imagine we have 100 ASP files and 20 layout functions similar to html_OneCellTable that need to be changed (indeed, in some cases all layout-related functions must be updated-e.g. when implementing a printer-friendly view). Making the necessary changes does seem to be a lot of work. What is more, that work needs to be done in one batch-once we've changed the function interfaces, we'll need to adjust all calls to these functions. Well, at least the last problem can be alleviated. The following section discusses how.
Gimme Little Help
Instead of redesigning the existing function, we can create a new one. We need to think of a different name for the new function as it has to live side-by-side with the old version. Once the new function is implemented, we can change the old function's body to call the new one with a static value for the new parameter(s). By doing so we allow for parameter-independent changes that will also be reflected in the old function. The following code illustrates this approach. The border parameter of the table is an example of a parameter-independent change.
- Original function uses the new one
' Original function uses the new one
Function html_OneCellTable(ByVal strBody, ByVal intWidth) html_OneCellTable = html_GetOneCellTable(strBody, intWidth, "blue") End Function
- The new function receives a new name
' The new function receives a new name
Function html_GetOneCellTable(ByVal strBody, ByVal intWidth, ByVal strBgColor) Dim strTemp strTemp = "" strTemp = strTemp & "<table border=""1""" strTemp = strTemp & " width=""" & intWidth & """" strTemp = strTemp & " background=""" & strBgColor & """" strTemp = strTemp & ">" strTemp = strTemp & "<tr><td>" strTemp = strTemp & strBody strTemp = strTemp & "</td></tr>" strTemp = strTemp & "</table>" html_GetOneCellTable = strTemp End Function
It is true, this trick does not solve the actual problem. We still will have to adjust all calls to the function if we want to use the new feature. But the advantage of creating a new function is that old code won't be broken. On the other hand, this approach introduces a new problem. It's rational to assume that while we designed the original function we chose the best possible name for it. Consequently the name of the new functions will be--at best--the second best alternative.
The question that arises is whether or not it is possible to preempt the situation described above. Charles Carroll, the founder of learnasp.com, makes an interesting suggestion.
His solution is to design functions with only one parameter of the type Dictionary. Charles' solution can be found @ http://www.learnasp.com/learn/subdictionary.asp.
[revision 1] The original version of this article wrongly asserted that Charles suggested one array-type parameter. Before I was able to fix this mistake, Marcus Tucker pointed out the benefits of the Dicitionary-object approach.
Here is Marcus' description:
- By creating a dictionary, adding key/value pairs, and passing it as the only parameter to a function/sub, new parameters can be added without impacting previous uses of the function in other code. The function merely needs to subsitute default values for parameters not found in the dictionary. This does have the downside of having to create and delete a dictionary object every time, but killing the object can be done by the function itself by ensuring that the dictionary is passed as a reference (use ByRef) and then setting this to equal nothing at the end of the function.
The approach with one array-type parameter originally mentioned in the article is quite similar but has one serious downfall. With this approach the possition of each parameter in the array is fixed. I.e. if out of 3 parameters you decide to leave out the second parameter, you will have to use a 3-element array with an empty element. Also, as a user of such function, you will have to memorize (or rather constantly look up) the positions of the individual parameters. Since with the Dicitionary-object approach you can use names for your parameters, it is much more comfortable.
In his feedback Marcus also mentions a class-based approach. Here's how he describes it:
- Another approach is using VBScript classes. I use this for encapsulating related functions and subs, and define public variables within it which can then be modified by accessing classname.variablename. This too allows new parameters to be added without impacting previous uses of the class (just ensure that the class initialises all variables with default
values at creation). Classes have the advantage of having low server overhead (lots of dictionaries in lots of sessions are BAD), and are self-destroying at the end of page scope.
Another option would be to encapsulate your logic into a component. Such component will be created with a fully-fledged programming language, thus allowing for optional parameters. On the other hand, though, by doing so you will be confronted with all disadvantages of component use.
Whereas all these methods enable us to be prepared for future changes, they actually accomplish as little as let us distribute the required code changes over a longer period of time without breaking the code. In that sense, they don't essentially differ from the method of creating a new function. The latter is the only one that can be helpful in an ex post scenario--i.e. if you haven't addressed this problem before it arose.
Summary
The missing support for optional parameters in VBScript may lead to time-intensive revamping of code if interfaces of widely-used functions need to be changed. Thanks to an appropriate strategy, the changes required to make the old application run with the new function can be spanned over a longer period of time. Which strategy will be chosen in the end strongly depends on the personal preference of the developer. Unfortunately, the manual job has to be done in any case.
|