Sokulski mentions that at present software is not automatically generative, but we have been told that software should evolve over generations. So why aren't we working on automatically generative software? Why aren't we working on software that grows or evolves automatically over generations of use as opposed to generations of changes? Which technologies do we have at present that support generative software, and which technologies are missing? The answers may not yet exist for those questions, but some capabilities in VS 2005 support generative code.
You can employ macros to write code for you. These macro code generators do not have to be random, and you don't have to rely on imagination or invention to figure out which kind of macros to write. Two independent but related fields of studydesign patterns and refactoringclearly offer a plethora of options for writing understood, well-documented code generators. (Code generation is not the same thing as generative code, but it is a cog in that engine.) This article demonstrates how to use the macro engine in VS 2005 to write a code generator that implements the refactoring Encapsulate Field for VB.NET.
Refactoring: Encapsulate FieldRefactoring is a defined process for improving the implementation of code. You achieve it by converting random constructs and idioms into a prescribed, predictably reliable use of constructs and idioms. In the simplest sense, refactoring takes some of the subjectivity out of code. As software engineers, we no longer have to depend on argument and force of will to determine whether code is good or not; we can employ an objective standard and agree that refactored code is preferable to code that is not.
Like design patterns, refactorings are named idioms that come replete with descriptions, instructions, and intended results. Any programmer, regardless of experience, can read the description, apply the ordered instructions much like following a recipe, and get a predictable improvement.
An example of a refactoring is called Encapsulate Field. Encapsulate Field is basically the name for making fields private and limiting access to those fields through public property methods. Limited access to an object's state is preferable to unlimited access, and Encapsulate Field is a belief in the value of constrained access to data. (Some people may not agree with the basic premiseconstrained access is preferable to unfettered accessbut some people don't believe objects are good. Rather than debate whether refactoring is good or bad, this article assumes that refactoring is good.)
Implement the MacroWhen you are in Visual Studio 2005 in the context of a C# project, a refactoring menu is present. In a VB.NET project, no such menu is currently present (at least by the time beta 2 was released). However, you can easily emulate this supported behavior for VB.NET by writing a lightweight code generator to implement Encapsulate Field (or other refactorings).
To implement Encapsulate Field, automate the following steps:
- Select a field without a coincidental property method.
- Change the field's access modifier to private.
- Change the field name slightly to avoid a property collision (using any convention you'd like).
- Generate the getter and setter property methods and lines of code to provide access to that field.
Tip: A good way to begin working with macros is to turn on the macro recorder, complete a task, and see which macro statements the IDE writes. Next, generalize the recorded macro.
The Visual Studio object model supports all of these capabilities and significantly more. Listing 1 demonstrates an implementation of Encapsulate Field.
Listing 1: Encapsulate Field Written as a Code Generator Using the Macros Capability of VS 2005
Imports EnvDTE Imports EnvDTE80 Imports System.Diagnostics Public Class Refactoring Public Shared Sub EncapsulateField() Dim projectItem As ProjectItem = DTE.ActiveDocument.ProjectItem Dim fileCodeModel As FileCodeModel = projectItem.FileCodeModel ' Get the current selection Dim selection As TextSelection = DTE.ActiveDocument.Selection ' Get the current cursor location Dim point As TextPoint = selection.ActivePoint ' Try to read the current location as a code element Dim codeElement As CodeElement = fileCodeModel.CodeElementFromPoint( _ point, vsCMElement.vsCMElementVariable) If (codeElement Is Nothing) Then MsgBox("Place mouse cursor on field before running this macro.", _ MsgBoxStyle.Exclamation) Return End If Debug.Assert(codeElement.Kind = vsCMElement.vsCMElementVariable) ' we've tested so we know its a variable Dim codeVariable As CodeVariable = CType(codeElement, CodeVariable) Dim fieldName As String = codeVariable.Name Dim fieldType As String = codeVariable.Type.AsString ' rename the field so we don't have a collision with property codeVariable.Name = "F" & fieldName ' make sure field is private codeVariable.Access = vsCMAccess.vsCMAccessPrivate ' get the variable's parent Dim codeClass As CodeClass = CType(codeVariable.Parent, CodeClass) ' add a new property Dim codeProperty As CodeProperty = codeClass.AddProperty("dummy", _ "dummy", fieldType, codeElement) codeProperty.Name = fieldName ' implement the getter Dim getter As EditPoint = codeProperty.Getter.GetStartPoint( _ vsCMPart.vsCMPartBody).CreateEditPoint getter.LineDown() getter.Indent(, 3) getter.Insert("Return " + codeVariable.Name) ' implement the setter Dim setter As EditPoint = codeProperty.Setter.GetStartPoint( _ vsCMPart.vsCMPartBody).CreateEditPoint setter.LineDown() setter.Indent(, 3) setter.Insert(codeVariable.Name + " = Value") End Sub End Class
To try this sample, open the Macros IDE from the Tools|Macros menu in VS 2005, and create a new module under the macro file MyMacros (accessible from the Macros IDE's Project Explorer).
The object model for the VS IDE is huge, so I won't try to describe it all here. In addition, the code in Listing 1 is sufficiently commented such that you can readily determine what each chunk of macro code does. Essentially, given the following field:
Public Foo as Integer
The macro adds an F-prefix to the field name (a habit the author picked up from writing tons of Pascal code) and changes the field's access modifier to Private. Finally, the complete property statement is generated and added to the module containing field:
Private FFoo As Integer Public Property Foo As Integer Get Return FFoo End Get Set(ByVal value As Integer) FFoo = value End Set End Property
Experiment with the macro code by stepping through it in the Macros IDE.