As soon as the first article of this Prime Programming Proficiency series hit CodeGuru.com, I received an e-mail response about the value of lines of code as a metric. I love the dynamic nature of the Web and the opportunity to exchange ideas with people all over the world. That said, Part 1 did not advocate lines of code as the best or only metric. It is part of a mosaic of information that should be collected as a means to an end—programming proficiency.
Lines of code are a relatively easy metric to obtain, and used prudently, it can tell you whether your code is growing or shrinking. Growing code could indicate more features, but it could just as easily indicate useless code bloat. Shrinking code could mean that features were removed or that refactoring is occurring. So, you see that counting lines of code creates more questions than it answers, but it is part of an information-gathering process that can be a first step toward obtaining the answers.
This second article of the series introduces the VS.NET Macros IDE and gets you started on implementing the LineCounter tool for VS.NET. Parts 3 and 4 will provide the complete code listing and discuss the pattern used to implement the solution, the Visitor behavior pattern.
VS.NET Extensibility Object Model for Projects
Visual Studio .NET has a comprehensive extensibility object model. Writing macros in VB.NET is a convenient way to access this part of VS.NET. (See the help documentation VSLangProj Hierarchy Chart for specifics on the extensibility object mode for projects.)
By using this object model in conjunction with macros, you or third-party tools vendors can write simple or advanced code generators or automate a wide variety of tasks to extend and customize VS.NET. In fact, by combining macros, wizards, the CodeDOM, project templates, and the extensibility object model, one can automate a wide variety of tasks and speed up software development significantly. (These features alone would justify having a fulltime toolsmith on your team, assuming you have more than a couple of developers.)
For now, let's look at using the extensibility object model and macros to design and implement your line counter utility.
Exploring the Macros IDE
As a consultant, I meet a lot of smart people. Oddly, though, few of them talk about the extensibility object model or even seem to know about macros. To give you an idea of the possibilities, consider some enhancements I recently devised:
- I created a New Web Page template that automatically employs 25 percent of a complete Web page for a recent project, including style links, HTML table layouts, footers, and headers. (Web page inheritance will be a nice upgrade eventually.)
- With the CodeDOM, I wrote a code generator that, when provided with a connection string and table name, generates a class and data class for that table, based on a custom data-access layer pattern. (XSD is a good alternative.)
- I implemented a property code generator as a macro and added it to the toolbar. So, a user provides the field name and data type and it writes the property code for him or her, and I implemented the LineCounter to see the evolution of the project by file and lines of code.
These functions are all automatic now, and they save a lot of time relative to the time it took to implement them. I hope this encourages you to explore all of .NET and share those explorations with others. (One of the best bangs for your buck is to join a users group. Another is to sign up for free e-mail newsletters such as CodeGuru.com's.) Now, I'll get back to macros.
Macros are supported by their own IDE. To open the Macros IDE, select Tools|Macros|Macros IDE from Visual Studio .NET. The Macros IDE is a lot like Visual Studio's IDE and the Macro language is Visual Basic .NET, so you should feel right at home.
A Quick Tour of the Macros IDE
The Macros IDE has a Project Explorer. When you expand the elements of the project being explored, you see VB modules—think shared class—and class files. These files are located in C:\Documents and Settings\[user name]\My Documents\Visual Studio\ MyMacros.vsmacros. (This is useful information if you want to share macros with others.)
You can create plain vanilla modules or classes in the project and import or export classes and modules. I haven't tried to use a form in the Macros IDE, but I suspect you could. The important thing is that an entry point for macro code is a public method in a module that requires no arguments and returns either void or a subroutine with no parameters. After the macro has started, you can run any combination of methods, create classes, and generally perform any kind of programming task. Listing 1 offers a quick, introductory sample macro to get you started.
Listing 1: Quick Macro Sample.
Option Strict Off Option Explicit Off Imports EnvDTE Imports System.Diagnostics Imports System.Windows Imports System.Windows.Forms Imports System Imports System.IO Imports System.Text.RegularExpressions Imports System.text Public Module MyUtilities Public Sub DumpProject2() Dim project As Project Dim projects As Projects projects = DTE.Solution.Projects For Each project In projects Try Debug.WriteLine("Project: " & project.FullName) Catch Debug.WriteLine("Project: " & "<no name>") End Try Next End Sub End Module
The example macro DumpProject2 obtains an instance of the Projects collection from the active solution—DTE.Solution—and iterates each project, writing the project's name to the Macros IDE's Output window. Some solution-level elements do not have a FullName property because they may not be projects in the traditional sense of the word. You also can examine the Project.Kind property to determine the specific type of the element. The constants for Kind are defined in the EnvDTE.Constants namespace.
Stating the Problem
Your primary problem is building a solution that performs the following tasks:
- Iterate over the current solution loaded into VS.NET.
- Examine every project, sub-project, and file.
- Track and print solution, project, and file names and sizes as lines of code.
The secondary problem is to accumulate totals for these same elements. (Don't you wish all project statements were so straightforward?) The final requirement is to print the element information and totals in the VS.NET Output window.
Having a statement of work, you now can begin defining a solution. The first thing you might do is extend your sample code from Listing 1 and just keep adding inner, nested loops to handle sub-projects and files. I will submit, with some caution, that this will work fine—and it reflects how a lot of code is written. However, that's not how this article is going to do it.
Designing a Solution
Although the statement of work guides the solution, it does not guide how you design your solution. To recap, the following is the statement of work listed as a set of imperatives (I really wish all projects were articulated this clearly. Oh, sorry, just daydreaming a bit.):
- The system shall be implemented as a VB.NET macro and callable from VS.NET.
- The system shall examine the currently open solution in VS.NET and fail miserably if no solution is open. (Hey, as long as we are being clear, we can be clear about failure states too.)
- The system shall examine all elements in the solution, including projects, sub-projects, source code elements, and non-source code elements.
- The system shall use the VS.NET Output window as the results pane.
- The system shall display the name of each project, sub-project, and file element as each element is examined.
- The system shall display the file name and line count for files with a .vb extension and a ProjectItem.Kind of EnvDTE.Constants.vsProjectItemKindPhysicalFile.
- The system shall keep a running count of the number of projects, total number of files, and total line count of all source files. (A file is considered to be a source file if it has a .vb extension and its ProjectItem.Kind is defined as EnvDTE.Constants.vsProjectItemKindPhysicalFile.)
- The final process shall display the total line count, total file count, and total project count.
Pretty easy, huh? Interestingly, most projects can be defined at this level of granularity using something as simple as an outline and word processor. Equally interesting is that very few projects have anything even remotely approaching this kind of definition statement.
At this point, your problem is defined and the solution is stated as a list of unambiguous imperatives. (If one were negotiating this problem and solution statement as a fixed-price contract, all that would be left to do is have a lawyer examine the language for problematic loopholes in verbiage. I would also include a good change management clause in the contract.)
Having the problem and solution statements, all you have left to do is implement and test the solution. In the final part of this series will do just that. As mentioned, you could hack out a bunch of nested loops, conditional statements, and the like, but using the Visitor behavior pattern will result in code that you can be proud to exhibit.
Don't Work in Ignorance
Lines of code are a heuristic that is akin to drawing a line in the sand. Unfortunately, measuring lines of code is only the first step in a journey of a thousand miles. You must isolate newly produced code and evaluate it relative to the schedule, complexity of the problem, and qualitative aspects of the code. Failing to do so is working in ignorance of measurable productivity and quality.
Part 3 of this article series will discuss the design pattern that makes the line counter utility more manageable. In addition to providing a team with a starting point for productivity improvement, all of these skills can be used collectively to solve other problems, which can improve a team's software development skill.
If you want to read more about the CodeDOM, project templates, or macros (other than what I cover here or in Part 3), check out some of my other articles or books. A Google search of my name and the subject you are interested in should do the trick.
Written by Paul Kimmel. Pkimmel@softconcepts.com
Copyright © 2004. All Rights Reserved.
Prepared for codeguru.com
Paul Kimmel is the VB Today columnist, has written several books on .NET programming, and is a software architect. You may contact him at Pkimmel@softconcepts.com if you need assistance or are interested in joining the Lansing Area .NET Users Group (glugnet.org).