Refactoring - the elixir of youth for legacy VB code

This article was originally published in VSJ, which is now part of Developer Fusion.
Standard refactoring techniques coupled with automated refactoring tools provide an excellent platform for upgrading legacy VB code. Legacy VB code suffers poor structure and ‘bloated code’ due to lacking inheritance and other object-oriented capabilities. Upgrading old VB code without any restructuring will not bring the benefits of .NET architecture. Restructuring legacy VB code in order to eliminate most obvious shortcomings can often help us discover much more efficient and robust design, and even code that we were poised to discard can find new life in the .NET world.

Introduction

It seems that in programming a new paradigm rarely replaces the old one, rather it builds upon its predecessor. These days there is a lot of talk about Aspect-Oriented Programming (AOP). It is an exciting new concept that, compared to Object-Oriented Programming (OOP), can express better certain intentions and sometimes provide better re-use. However, it is a general consensus that it’s not yet poised to replace OOP.

Since the advent of COM, VB programmers were (albeit, often unknowingly) on the forefront of the new paradigm, “component based programming”. They learned how to assemble components and how to treat them as black boxes with well-defined, self-describing interfaces. It proved to be an effective way to assemble a GUI, by combining different “controls” and creating new ones, or to assemble and separate business logic inside components. It also promoted encapsulation, since “inheritance breaks encapsulation”, and VB, while incorporating the “interface” construct, had no implementation inheritance capabilities.

On the other hand, VB programmers were often treated as the pariah of the programming community. Java programmers frowned at the fact that VB had no inheritance, while C++ programmers had a difficult time getting over the fact that VB had no pointers and almost no direct memory manipulation. VB was often called a “toy language”. The tools and the language also carried a lot of baggage from the past, like the ability to use global functions and data, sub-procedure control statements and syntax that was rarely to anyone’s liking, to name just a few such issues. Even the boundary between tools and the language was rather blurred.

While it is possible to write good code in VB, the tool’s RAD nature often resulted in code in which quality was evidently not a major consideration. It is easy to write code where lines proliferate, and structure and reuse are often lacking. For certain small projects, visual development is remarkably effective. On the other hand, with VB’s entry into the enterprise, especially with MTS components or COM+, and Microsoft’s distributed “DNA” architecture, and evolution of client-server, developing robust software with VB became increasingly difficult. Still, VB has won a role in the enterprise and it is a popular tool for proprietary software development.

These days VB .NET has most of the characteristics of a modern OO language. There has been a major shift from its previous version and a new direction in its programming paradigm, marking a return to basic OO principles. While components are still there, VB now boasts inheritance.

A lot of organisations are now in a situation where they will need to maintain legacy VB code alongside code newly developed in VB .NET. It is easy to imagine why this fact implies additional cost. Development teams need to maintain a twin focus, on interoperability and re-use. While this is possible, it is difficult, and many benefits of the new platform are not available to the legacy programmer. The old tools will lack manufacturer support. This almost certainly marks a turning point for an enterprise, since outdated information technology can hamper business and limit its capacity to respond quickly to new challenges. For example, thanks to its underlying platform VB .NET is “web service ready”, while productivity of VB6 in this area is much more limited. And while differences between VB .NET and prior VB versions are significant, it still provides a good basis for upgrade and decent tool support for this purpose.

In this article, we will take a look at some common VB 6 idioms. We will use a popular VB 6 “Engine Collection Class” pattern article from MSDN, with source code downloadable at microsoft.com to illustrate how can we migrate code while trying to reap the benefits of new language capabilities. We will see that common refactoring transformations provide an excellent basis for upgrade.

Please note that we will not dwell on language and syntax details, since there are tools and guides that can lead you through this process. Suffice to say, we need to get our VB 6 code working inside the VB. NET IDE with a bare minimum of changes applied. We advise making the most of the changes in VB. NET because of its stricter language and better testing framework support. We can make use of the “Code Advisor”, a free tool from Microsoft that works as a plug-in for VB 6. It parses the code and makes “FixIt” comments, marking parts of code that will not be migrated automatically. It also permits adding custom code-checking rules.

One hint is that conditional compilation tends to get in the way of the upgrade process, so one useful trick is to replace conditional compilation variables with global (module) variables and conditional compilation keywords with ordinary conditions. Replace:

#If DebugVersion Then
‘...
#End If
…with:
If DebugVersion Then
‘...
End If
…and declare the variable in modAccount:
Public DebugVersion As Boolean
After upgrade, we can erase global variables and turn conditional compilation back on.

Hopefully, we’ll have some testing harness in place that can help us make these modifications safely. If not, we can get hold of an automated testing tool and generate it. This should help us open up our project with the Visual Studio .NET IDE.

Warming up the cauldron

Transmutation No. 1: Write unit tests
Before we start making any changes to our code, we need to make sure we don’t break it instead. We can use the open source NUnit framework to help us with this task. NUnit is a .NET port of the well-known unit testing framework in Java – JUnit. Working with existing code can be very challenging for the test-afflicted programmer, since adding tests to existing code as opposed to writing code and testing simultaneously poses a lot of extra difficulties. One approach to the problem is to look for an “inflection point”, a narrow interface to a set of classes. Once the inflection point is identified, we need to cover it with a new unit test. With time, more and more code will be under our testing harness, giving us confidence when changing and modifying code.

Transmutation No. 2: Replace Variant with Strong types and deal with optional parameters
Variant is a “universal” data type in VB 6, meaning it can act as a wrapper for any type in VB 6. In the ECC pattern extensive use of Variant is advised, for reasons of flexibility when used by scripting clients. There is no such limitation in VB .NET so we will restore strong type checking by replacing all variants with definite types. Things can get a bit complicated when code explicitly checks for the underlying type of a Variant and bases its logic on it.

Having upgraded our VB 6 project to VB .NET with theVisual Studio .NET IDE, all Variant declarations will be automatically replaced with Object declaration, so we are better off doing this in a more controlled manner, even if this does imply manual conversion.

VB .NET does not understand the IsMissing function: if we have based the way our code is structured on this function, we’ll need to find another way to check for optional parameters. The simplest way to solve this problem is to declare a “special value” as a default parameter value. We must be sure that under no circumstances can a parameter hold this value. For example, a collection index can never have a negative value. Instead of checking for IsMissing, we’ll check instead for a special value. If a parameter has the special value, it means that an optional parameter was not used. In order to make the upgrade process more straightforward, we can add our own private IsMissing function to our new VBUpgrade module; this way the original VB IsMissing function from the VBA.Information module will be shadowed and our function will be executed.

Here is the code snippet:

Private Const _
IS_MISSING_OPTIONAL_PARAM_INT _
As Integer = -1
‘We could write similar functions for
‘ other types (for example
‘ String etc.)
Public Function IsMissing( _
ByVal Param As Integer) As Boolean
If Param = _
	IS_MISSING_OPTIONAL_PARAM Then
	IsMissing = True
Else
	IsMissing = False
End If
End Function

‘Sample function with optional
‘ parameter declaration
Private Sub Releasing( _
Optional ByVal Resource _
As Integer = _
	IS_MISSING_OPTIONAL_PARAM)
If IsMissing(Resource) Then
	‘Parameter is missing
Else
	‘Parameter is NOT missing
End If
End Sub

Main ingredients

Transmutation No 3: Introduce inheritance
While it was possible to simulate inheritance in VB6 by combining interfaces and delegation, this approach is tedious and in practice rarely used. This gives us a lot of scope for the types of transformations we have in mind. In VB .NET we can use “Extract Superclass” refactoring to provide for better reuse and better design in existing code. We can see that in the ECC example all “Class” types (as in Engine-Collection-Class), share some common methods. We will start off by applying refactoring. For example, we will break a CAccount class into two by extracting a new superclass: ECCClass. The ECCClass superclass holds all the elements that appear in all “Class” types: properties IsNew, Dirty, DeleteFlag, ClassStorage and SecurityToken. Since there is no sense in instancing ECCClass directly, we’ll mark it as abstract by using the MustInherit keyword in VB .NET. All classes that use the CAccount class need not be modified; our transmutation is completely transparent for them.

Transmutation No 4: Extract Class
We need to give a “Collection” a bit more thought. The first thing to notice is that it acts both as a container and as a persistence mechanism for the “Class”. These are clearly two separate responsibilities. We are better off keeping them distinct. We can accomplish this by applying the “Extract Class” transmutation. We will move the Update and Delete methods to a new “AccountPersistence” class. We can see that these methods actually iterate over all “ECCClass” objects in the collection checking if some of them are new, dirty or marked for deletion. Once they find such an object, they issue an appropriate command to the database – INSERT, UPDATE or DELETE – and so persist these changes. Our new persistence class has no reference to the collection of objects it needs to persist, so we will add an AccountCollection field to our AccountPersistence class. This field is of the ColAccount type and will be given values by the AccountPersistence constructor. The AccountPersistence constructor we need to add will receive a single parameter, ColAccount. ColAccount, on the other hand, will, inside its own constructor, create an internal AccountPersistence instance; will pass itself as a parameter of the AccountPersistence constructor; and set the new AccountPersistence field with this value. This is the way to ensure that the original and new class keep maintain a reference to each other.

We will keep the Delete and Update method declarations in ColAccount, but these methods are now going to dispatch all calls to the AccountPersistence instance. This all sounds a bit complicated, but after looking at a code sample you’ll see that there’s not much to it.

Transmutation No 5: Extract Method – replace optional parameter with overloaded methods
Thanks to the ability to overload methods in VB .NET, we can make some further adjustments to our Persistence class. We can see that the Update and Delete methods contain single optional parameters in the declaration. Basically we can perform update or delete either on a single Account when the parameter is specified or on the whole collection when every Account is inspected for a new/dirty/marked for deletion flag and saved, updated or deleted accordingly and no parameter is given in the method call. We can conveniently extract the code where this check for an optional parameter is performed. We can then separate the conditional logic into two methods; one with, the other without a parameter. Both of them will pass a call to the new private Delete method with two parameters: LowerLimit and UpperLimit.

Our client code will not break after this transmuation since we can still call the Update and Delete methods with or without a parameter. The correct method is called thanks to the overloading mechanism.

Transmutation No 6: Replace delegation with inheritance
In VB .NET a standard way to implement a collection class is to extend some of the classes already provided by the .NET framework. This works well for our ColAccount class. We can inherit the CollectionBase class. We don’t need to implement the System.Collections.IEnumerable interface since CollectionBase already implements it. We can delete the GetEnumerator, Clear and Count methods, and the internal mcoll Collection variable and use the default implementation from the CollectionBase class that our ColAccount class now extends.

We can observe another characteristic of our ColAccount class. Since all persistence code was removed in transmutation no. 2, our collection does not need to have knowledge of the Data Access Layer. Also, in the majority of methods it can manipulate all ECCClass descendants in the same way, handling them via the superclass.

Transmutation No. 7: Dealing with persistence class: Extract Super
The AccountPersistance class has a few quite long methods. We should reorganize them, if only to make them shorter. In this case we have an additional purpose, to separate the more “generic” code from Account type specific code. At the same time we will replace more specific references to CAccount with references to ECCClass.

From the Delete method we can extract PrepareDeleteData that sets deletion parameters and we can replace calls to the stored procedure name constant with a StoredProcedureDelete method. Similarly, from the Update method we can extract PrepareUpdateData, StoredProcedureInsert, StoredProcedureUpdate and UpdateCollectionWithReturnedData.

What we are actually doing here, is preparing “hook” methods; once we extract the superclass only these methods will need to be implemented.

Now we can extract Super. We can call it ECCPersistence and we will mark it as abstract (MustInherit). It will receive Add, Insert and Delete methods. It will also declare all our Account-specific methods abstract – MustOverride: PrepareDeleteData, ExecuteDelete, PrepareUpdateData, ExecuteUpdate and UpdateCollectionWithReturnedData. Our AccountPersistence class already implements these methods.

Transmutation No. 8: Dealing with collection class: Introduce constructors and extract subclass
Let’s take a look at the Add method in ColAccount. A large part of the method is concerned with setting up new account instance properties with default values. We can extract this code to a new SetUpDefaultValues method. This code should be executed at the moment of instance creation and it is really concerned with CAccount data. This excessive interest in the data of another class can be considered a “smell” in the refactoring sense of the word. Additionally, VB .NET supports constructors, so we are better off moving this code to CAccount class and calling it from the CAccount constructor. This is known as “Move Method” refactoring.

Since SetUpDefaultValues is called directly from the constructor, we can “inline” this method, and place all the code inside the constructor. We might have done all this in one go, and moved code directly to the constructor. The advantage from doing it step-by-step, as described, is that we can use tools to perform refactoring, and that is much safer and faster.

Now we will extract the new Add method from the old one. It receives a single ECCClass parameter, thanks to the overloading capability in VB .NET. It will receive a CAccount instance and it will add it to the InnerList. We will modify the existing Add method code so it calls the new Add method.

We can continue with identifying and defining new levels of abstraction. Since our ColAccount class already extends CollectionBase, we need to have a new approach. This time, we need to perform “Extract subclass” refactoring on our ColAccount class. First, we will rename it to ECCCollection. Now, we will replace references to CAccount by references to its super, ECCClass, and references to AccountPersistence by references to ECCPersistence wherever possible. After this, we can extract a subclass, conveniently named ColAccount, that has only one method, Add. This is the only method referencing CAccount class. After this, we need to go back to our ECCPersistence class and replace all references to ColAccount by references to ECCCollection.

If we take a look at the static structure diagram in Figure 1, we can see that there is a layer of abstraction appearing with most of classes now belonging to a certain hierarchy. IOBPAccountEng is still the odd one out, but we will deal with it soon.

Figure 1
Figure 1: An emerging hierarchy

Transmutation No. 9: Attributes
VB .NET supports a new metadata construct – attributes. In some cases, .NET framework services can be implemented as simply as marking certain element with an appropriate attribute. Serialization is one of those services – all we need to do is mark a class with <Serializable()> to be able to persist this class to a file.

We will mark our ECCCollection and ECCClass with <Serializable()> attributes and we will erase their Store methods that had a similar, but more limited, purpose. In this case, we will break compatibility and let the client code be modified accordingly, since in the example provided the client is not making use of this functionality.

Transmutation No 10: Introduce Factory Pattern
An Engine class can be transformed along similar lines as a Collection and Entity class. An Engine class can help us control the creation of ECCClass objects and its initial state. It also separates database query code from our business layer. Following the usual procedure, we will define an abstract (MustInherit) ECCEngine class and “move up” the methods NewAccount, renamed to NewClass, and GetAccount renamed to GetClass. If we inspect the other modules, most of them use a simple primary key, so we can use the definition of the GetClass method throughout our application. In case this changes, we’ll represent a primary key by the type on its own.

In IOBPAccountEng, we will delegate calls to new methods in super. We are “upcasting” the code, so it references classes higher up in the hierarchy. We need to defer the decision on what concrete type of ECCCollection or ECCClass our Engine will be instantiating to a class that extends it, so we will define an abstract CreateCollectionInstance method, by extracting into the method a line that explicitly creates a ColAccount object:

oColAccount = New ColAccount
…into:
oCollAccount =
	CreateCollectionInstance()
The method declaration is:
Public MustOverride _
CreateCollectionInstance() _
As ECCCollection
This way we provide a “hook” for an implementing class to decide on what type of object to create. Needless to say, the object in question needs to extend ECCCollection.

Our IOBPAccountEng can now implement this method and return a new ColAccount.

We will leave the Search method “as is”, since it depends on CAccount class.

Transmutation No 11: Extract Interface
At this point our “mixture” is doing quite well. Still, if you have a refined sense of “smell”, there is still a faint odour coming out from our code stew. And, since duplicated code is number one on the list of “refactoring smells”, we definitely need to deal with it.

There is one property scattered and repeated throughout our code. It is the SecurityToken property, used to provide security in our application. Without giving it too much thought, we can extract this property into a new abstract (MustInherit) SecuredObject class. Most of the classes that were implementing this property will now only extend the new SecuredObject class. This solves the duplication issue.

Now, thinking about this property, we can see it has a completely different responsibility compared to rest of the code in each class. We can make this more explicit by extracting a new ISecured interface that will declare only one, SecurityToken, property. This makes the intent more obvious. There is another advantage in using an interface. VB only supports single inheritance, but interfaces can be multiply inherited. So even if our class needs to extend another class it can still “inherit” ISecured.

Pure Gold: An emerging framework

We can now clearly see that the topmost classes in the hierarchy represent a repeatable pattern that can be equally effectively used in other projects. They help us enforce design decisions and rules on a lower level than the design pattern we started off with. Now, only a small portion of code is left to free interpretation and we have a lot of code we can reuse, so we do not need to start from zero. Clearly, this is not just a design pattern anymore. We have a framework emerging!

Transmutation No 12: Rename
We have come far from the original code we started off with. We have modified our code enough to say that new concepts and new idioms have been spawned. To make our code more understandable, we will execute another modification to our code, “Rename refactoring”.

We can rename our ECCClass to Entity, our ECCCollection to EntityCollection and our ECCEngine to Factory (see Figure 2).

Figure 2
Figure 2: The Emerging Framework

Transmutation No. 13: Manage dependencies with VB namespaces and “Move class” refactoring
As a final step in building our framework, we need to separate classes belonging to our newly developed framework from classes belonging to the specific implementation. Another point to bear in mind: higher-level abstractions need not depend on those at a lower level.

VB .NET introduces the concept of Namespace. In previous version of VB namespaces were implicit, intertwined with compilation units and had only one level. In VB .NET namespaces are hierarchical and have no relation with source code or compilation units.

The solution therefore lies in defining a new namespace for our framework classes. I will call it “TeslaTeam.RefactoringVB.RefactoredECCFramework”.

We will perform Move Class refactoring in order to reallocate our framework classes to this new namespace. In VB .NET we can have multiple namespaces and classes in the same source code file, but our framework classes should end up in source files separate from the rest of the application specific classes (see Figure 3). Once we start to distribute the framework to our clients, we do not want our application classes being distributed as well!

Figure 3
Figure 3: Using the Framework

Transmutation No 14: Option Strict On
The Unit tests that we constructed at an early stage should provide a good coverage of any errors we might have made. They are our main instruments for making sure things we change don’t break our code. Still, if we want to make the process more robust, we can prevent implicit type conversions and we can switch “Option Strict On”. Then, we will need to perform all our conversions in an explicit way. This will give even more security to our code.

Seeing the future

We will now briefly describe a new feature announced for the “Whidbey” version of Visual Basic (see msdn.microsoft.com/vbasic/whidbey). It can help us create more efficient and type-safe code. Actually, it will eliminate the need for one class in our framework.

Future Transmutation No. 1: Refactor Collection to generic
We can see that our ColAccount is very similar to other collection classes in the framework. However, it is difficult to isolate this common behaviour in the form of inheritance because most of the differences come from a type that the Collection manipulates and for which the Collection serves as a container. We could have an implementation where all ECCClass children are treated as its common super type, but this would make us perform a lot of cast operations and would work against type safety. For example, we could place instances of CAccount and CBill inside the same container. The current version of VB .NET cannot help us avoid this. But, the Whidbey version of VB .NET boasts another important language enhancement, generics.

Generics will permit us define a Collection in a “generic” way, as a form of template that will receive its definitive form at the moment it is instantiated.

We will define a collection, according to the current specification, like this:

Public Class ECCColl(Of ItemType)
We will modify ECCAccountEngine class to instantiate the collection:
Dim coll As New ECCCollection(Of CAccount)
We have just eliminated the need for further subclassing in our framework. This makes our framework simpler and easier to use!

A step further: Refactoring GUI transmutation

Our ECC code example also includes code that demonstrates pattern use through a fully functional application that performs basic maintenance: Search, Insert new, Update or delete ECCClass types. Since we applied behaviour-preserving refactoring techniques to previous parts of code, our GUI code requires no changes and it is still fully functional.

As typical VB6 code, it suffers from a lot of already described “smells” and it can benefit greatly from refactoring. What’s more, we will try to do it in such a way that we can enlarge our framework with some ready-made GUI framework solutions.

The ECC example is essentially a pattern for a tiered architecture. What this means is that there is loose coupling between tiers and dependencies generally go from database to business to UI tier. While we can provide a UI solution inside our framework, it will in no way stop a framework consumer from building their own and completely new UI solution while reusing the rest of our framework.

Visual component inheritance

We can see there is great similarity in each of our CRUD forms, they all display and manipulate data in similar way. We will start off by extracting a superclass, we can call it ECCCRUDForm. This class will contain the majority of methods from our frmAccount class except two: FillForm and FillCollectionFromForm, since these methods need to manipulate the CAccount class. We can still declare them as abstract and replace the CAccount parameter with ECCClass type. The Subclass that will implement these methods will cast them to appropriate ECCClass subtype.

Our ECCCrudeForm need not have any knowledge of any specific classes, it needs to reference only ones pertaining to our newly-built framework. So we will replace all references to ColAccount with ECCCollection, and references to CAccount with references to ECCClass. It will also contain all data manipulation controls (Update, Cancel, Delete etc.). Our frmAccount class will contain code that adds the controls needed to display CAccount data.We can see that this refactoring is easily propagated to the rest of the CRUD forms, providing another point for reuse in our framework.

Conclusion

We started out by trying to eliminate the most obvious shortcomings in our code. We tried to purify it, eliminating duplication and simplifying over long methods and large classes. However, very soon we saw strong abstractions appearing. With only a little guidance while refactoring, a comprehensive, more robust and extensive new design has appeared.

We have seen how even well thought-out legacy VB code easily lends itself to refactoring, and how it can be a powerful tool for upgrading our code. A change in perspective on the value and usability of existing code is definitely taking place.

VB is now a fully capable OO language and major benefits are to be derived from making deep changes to design and to our way of thinking, by using new paradigms and language capabilities. It may not be a philosopher’s stone, but it is definitely a very useful tool in the expert VB programmer’s toolbox.


Danijel Arsenovski is a senior developer and consultant from Santiago, Chile. His interests include advanced OO programming techniques and refactoring. Recently, he has been recognised as Microsoft MVP.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Theory is when you know something, but it doesn't work. Practice is when something works, but you don't know why. Programmers combine theory and practice: Nothing works and they don't know why.”