Thursday, March 23, 2006

Microsoft doesn't do Encapsulation.

Microsoft has a big problem with encapsulation. It's tools don't support it, it's advice goes against it and your average VB.NET programmer has little idea what it means. I'm currently working on an enterprise application, written in VB.NET and working alongside a team of VB.NET developers. Now, back in a past life, I too was a VB developer (although with a strong interest in Java and C++) and I wrote applications around recordsets, but over the last three years, since I started doing C#, I've been infected by reading lots of Martin Fowler, Eric Evans, Rocky Lhotka and the Domain-Driven-Development agile crowd. Now I want to write Domain-Driven object-oriented applications. I've managed to steer my current team away from datasets but I just can't get them to understand why encapsulation is good and that's mainly because Microsoft don't seem to get it either. So what do I mean? Take the XML Serializer that's used by web services. In order to serialize an object, it has to be written in a particular way. It has to have a default public constructor and all its properties have to be gettable and settable. Here's an example Person object that can be XML serialized:
class Person
{
 string _name;
 int _age;

 public Person()
 {
 }

 public string Name
 {
  get{ return _name; }
  set{ _name = value; }
 }

 public int Age
 {
  get{ return _age; }
  set{ _age = value; }
 }
}
So what's the problem? Well, say we want to have a business rule, such as 'a person's name must be at least have at least one character. How do we enforce this? Well we can write some validation on the Name property setter:
public string Name
{
 get{ return _name; }
 set
 {
  if(value == null) throw new ArgumentNullException();
  if(value.Length == 0) throw new ApplicationException("Name must be at least one character");
  _name = value; 
 }
}
But that doesn't stop a developer writing:
Person myPerson = new Person();
and never setting the Person property. Now you have an invalid person object floating around in your application. You've effectively given up the power that object oriented development gives you to create classes that enforce their rules. Business rules about a Person are now leaking into the rest of the application. Without a parameterised constructor, and read only properties, you have to start relying on convention; rules like: "you must not store a Person object to the database without first setting it's Name property". The object can't protect itself so the developer has to start remembering a ever increasing list of do's and don'ts. My mental stack usually overflows after a handfull of arbitary rules. When I suggested writing properly encapsulated business classes to my team I came up against three main sticking points: 1. You can't pass encapsualted objects over web services (our communications layer is a web service). 2. You can't use form data binding with encapsulated objects (how do you start the new Person form with a blank name?) 3. You can't restore an encapsulated object from the database. Although I can answer all three objections: 1. Web services aren't meant for internal application communication. That's what remoting is for. 2. See my ealier post on using a nested builder class to solve the form databinding issue. 3. You can use reflection to restore the internal state of an object. (Hey, but that's three times slower than a property access! Perfomance old chap!) It was a step to far. It's not the way that Microsoft suggests working in most of it's architectural publications and it's not the way that it's tools support. You tend to have to work around the default behaviour. The little that I've read about LINQ (and it is only a very little) suggests that it wont support persisting the internal state of an object either. I was very pleased to see Rocky Lhotka feels the same pain that I do. Update: I was wrong about DLinq. Of course I should have read a little more about it before commenting:) Today I read the DLinq overview. DLinq uses attributes on domain classes to specify the object relational mapping and those attributes support internal field access, so it can persist the external state of a domain object without invoking property accessor business rules. Actually reading about it made me slightly more enthusiastic. So long as they get around to implementing polymorphic types and many-to-many relationships, I could probably live with attributed domain objects, although it smells of ORMapping in the domian layer, something that I'm against, being a big fan of layered de-coupled architectures.

Tuesday, March 21, 2006

Stevey's Drunken Blog Rants

I've really been enjoying reading Steve Yegge's blog, and that's not only because it's got the word 'rant' in the title:) He's an opinionated amazon software engineer and I seem to agree with most of what he says. Although that could be because I'm easily led.

Thursday, March 16, 2006

To stored procedure or not to stored procedure?

I've just been reading this post by Frans Bouma 'Stored Procedures are bad m'kay' where he rebuts some of the usual arguments about why you should use stored procedures; security, performance, encapsulating database access. I've been doing enterprise application development with SQL Server for around ten years now and every application I've worked on we've used stored procedures. I was told that stored procedures are good on my SQL Server 6.5 course all those years ago and have never really questioned that view. And I'm not alone. Pretty much every developer I've met over the years has shared this belief that stored procedures are god's own data access tool. On my current project, any suggestion not to use them would be met with horror. Hmm, but I'm begining to question this view, just like Frans Bouma. A couple of things are begining to sway me away from procs. Firstly, Frans makes a good case for rebufing the security and performance concerns, but I think more importantly there is the shift of emphasis in application development away from the relational data model towards the object oriented domain model. This shift is much more advanced in the Java world than it is the Microsoft camp, but us lot are begining to listen to the Java people. If you start to believe that the database should be merely a data persistence tool then the whole API argument with lots of business logic in the procs starts to look less viable. Instead the Elephant in the room these days is the often quoted 'object relational impedence missmatch'. Maintaining all that data access code and all those CRUD stored procedures becomes a major headache. On my current project I've mitigated this to a certain extent by building a code generator that builds all our CRUD procedures and .net wrapper code for us (probably re-inventing the wheel to a certain extent, now that I've discovered Code Smith) but it's still a headache. I haven't used NHibernate or any other OR tools, but I'm getting keener to try. Anything that automates all that boring data access stuff has to be worth a try. So, stored procedures? Well I wouldn't be at all surprised if I'm not using in them in a year or so.

Wednesday, March 15, 2006

A Solution for the reference data problem

What is 'the reference data problem'? In our Winforms application we cache reference data at startup. We hold a reference to that reference data using the ReferenceData class for the lifetime of the application and use it to populate combo boxes and such like. But reference data also enters the application throughout its lifetime as properties of our domain objects. When we get a party object, it comes populated with a gender, a country and other reference data instances. The problem is that we now have multiple versions of the same thing. The party object has a gender, "Male", with an id of 2 (for example), but we also have our cached genders, which include a gender, "Male", with an id of 2. What's the problem with having multiple versions of the same thing? The main issue is that we can't do reference based comparasons; 'myParty.Gender is male' (where male is an instance of Gender). So, you say, of course we can compare the IDs 'myParty.Gender.Id = male.Id', and that's true, but the dot net framework doesn't know about our IDs, so if you try myGenderComboBox.SelectedItem = myParty.Gender it doesn't work as expected. A lot of stuff is much much easier to do if you only have one version of the 'Male' object. So how do we get around this problem? Well it's relatively simple, we just need a few things in place: 1. A way to mark our reference data classes as reference data. 2. A way to cache our reference data in a way that makes it easy to find. 3. A way to set all our domain object's properties that are reference data, to our cached instances. 1 and 2 are easy, 3 is a little bit more involved. To mark our reference data as reference data we can use an attribute. This was a nice from Anthony, one of my fellow developers. I created a new attribute in 'ReferenceDataAttribute'. Now we can just put this attribute on any reference data class, like this:
<ReferenceData()> _
Public Class Gender
    Inherits Entity

End Class
2 and 3 are solved by our old friend the ObjectStore (oops I need to blog about this!) and some changes to our ServiceProvider class. This is the class that provides instances of services; DAL or Web service proxies. We use it like this:
Dim partyService As IPartyService = _
            AIS.Service.ServiceProvider.GetServiceInstance(GetType(IPartyService))
I've used a variant of the Gang-of-Four 'Decorator' pattern here so that when we ask for a service instance, we don't get a reference to a DAL or Web service, we get a reference to a 'Filter' that then in turn holds a reference to the DAL or web service. The Filter intercepts all the calls to the service allowing it to do numbers 2 and 3 above; cache the reference data when it's first loaded and map domain object's properties back to the cached reference data objects as they are loaded. The class that actually does all the work is called ReferenceProvider. Each filter has one of these. They are created by a ReferenceProviderBuilder object which our ServiceProvider class holds a reference to. We use the ReferenceProviderBuilder property of ServiceProvider to set up a new ReferenceProviderBuilder at application startup. The ReferenceProviderBuilder creates and holds a reference to the ObjectStore that's used to cache and retrieve reference data objects and then hands the object store to every ReferenceProvider that it creates. The ReferenceProviderBuilder class also has a property 'Mode' which is a enum with two values; 'Load' and 'Filter'. When the state passed to a ReferenceProvider is 'Load' it caches any reference data it sees in the object store, when the state is 'Filter', it looks for any reference data references in the returned object graph and binds the property to the cached referenced data object instead. So what does the developer have to do to use it? 1. Attribute all their reference data classes with the ReferenceDataAttribute as above. 2. Make sure that those reference data classes are cached at startup. 3. Don't get the reference data instances from the Data Access Layer when building object graphs.

Thursday, March 09, 2006

Doing remote procedure calls using WSE web service attachments

Note this is not an original idea. I'm sure I saw someone describe something similar on a blog a while back. I couldn't find the original source so I ended up re-implementing the same idea. WSE has a nice feature, web service attachments, that allows you to attach a binary file to a web service call. Now it doesn't take a big leap of imagination to see how you could use this to create a kind of remote procedure call. You'd serialize your parameters at the client and attach them to your standard call function. At the server end you retrieve the attachment, de-serialize your parameters, do whatever, serialize the return argument and return the call. Back at the client you de-serialize the return argument and it's done. Simple. Since the application I'm currently working on (the Civil Aviation Authority's Aircraft Register) describes all its service calls as interfaces it also means we can easily generate the client code. Now, you're thinking; "why doesn't he just use remoting?". That's a good point but this does give us a few benefits. We only have to maintain a single web method. We can use all that good stuff in WSE like security and routing. We could also implement compression easily by compressing the byte stream before we attach it. But most importantly we can still say to our managers, 'of course we're still using web services' so that they can live their SOA dream without it messing up our application design:) OK, now for an example. Let's use the canonical Add function, so our 'database layer' code looks like this (sorry they make me use VB.NET here... urgh):
Public Class MathService
    Implements AIS.Domain.Mock.IMathService

    Public Function Add(ByVal a As Integer, ByVal b As Integer) As Integer Implements Domain.Mock.IMathService.Add
        Return a + b
    End Function

End Class
Notice that it implements IMathService, our service interface. Our service client looks like this:
Public Class MathService
    Implements AIS.Domain.Mock.IMathService

    Private INTERFACE_TYPE As Type = GetType(AIS.Domain.Mock.IMathService)

    Public Function Add(ByVal a As Integer, ByVal b As Integer) As Integer Implements Domain.Mock.IMathService.Add

        Dim genericService As New AIS.Service.Generic.Client.GenericService
        Dim result As Object() = genericService.GenericFunction(INTERFACE_TYPE, "Add", New Object() {a, b})
        Return result(0)

    End Function

End Class
This also implements IMathService. It just takes the parameters and calls GenericService.GenericFunction passing in the interface, the method name, "Add" in this case and the parameters. GenericFunction looks like this:
Public Function GenericFunction(ByVal interfaceType As Type, ByVal methodName As String, ByVal parameters() As Object) As Object()

    Dim formatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
    Dim parameterStream As New System.IO.MemoryStream

    Dim serviceParameter As New serviceParameter(interfaceType.FullName, methodName, parameters)

    formatter.Serialize(parameterStream, serviceParameter)

    Dim returnStream As System.IO.Stream
    returnStream = InvokeAttachmentFunction(parameterStream)
    Return formatter.Deserialize(returnStream)

End Function
It wraps the interface name, method name and parameters into a ServiceParameter object, serializes it and passes it to InvokeAttachmentFunction, which looks like this:
Private Function InvokeAttachmentFunction(ByVal parameterStream As System.IO.Stream) As System.IO.Stream

    Dim proxy As New GenericServiceProxy
    proxy.Url = m_configuration.ProxyConfiguration.ServerUrl

    ' Create a new DimeAttachment class, and add the stream to that
    Dim attachment As New attachment("text/plain", parameterStream)
    proxy.RequestSoapContext.Attachments.Add(attachment)

    ' make the call
    proxy.GenericAttachmentFunction()

    ' retrieve the return attachment
    ' test for attachments
    If proxy.ResponseSoapContext.Attachments.Count = 0 Then
        Throw New ApplicationException("No attachments detected")
    End If

    ' return the object stream
    Return proxy.ResponseSoapContext.Attachments(0).Stream

End Function
This creates a new GenericServiceProxy (generated by the WSDL.exe tool), attaches the parameter stream to the request, calls the web service, gets the returned attachment and returns it. Our web service looks like this:
<WebMethod()> _
Public Sub GenericAttachmentFunction()

    ' check there's at least one attachnent
    If RequestSoapContext.Current.Attachments.Count = 0 Then
        Throw New ApplicationException("No attachments detected")
    End If

    ' deserialize the attachment stream to a serviceParameter object
    Dim parameterStream As System.IO.Stream = RequestSoapContext.Current.Attachments(0).Stream
    Dim formatter As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
    Dim serviceParameter As serviceParameter = formatter.Deserialize(parameterStream)

    ' invoke the given method on the given interface with the given parameters
    Dim result() As Object = Invoke(serviceParameter.InterfaceFullName, serviceParameter.MethodName, serviceParameter.Parameters)

    ' serialize the result to a stream
    Dim resultByteStream As New System.IO.MemoryStream
    formatter.Serialize(resultByteStream, result)

    ' create an attachment with the stream
    Dim attachment As New attachment("text/plain", resultByteStream)
    ResponseSoapContext.Current.Attachments.Add(attachment)

End Sub

Private Function Invoke(ByVal interfaceFullName As String, ByVal methodName As String, ByVal parameters() As Object) As Object()

    Dim service As Object = ServiceProvider.GetServiceInstance(interfaceFullName)

    Dim serviceType As Type = ServiceProvider.GetServiceInterfaceType(interfaceFullName)
    Dim method As System.Reflection.MethodInfo = serviceType.GetMethod(methodName)
    Dim result As Object = method.Invoke(service, parameters)
    Return New Object() {result}

End Function
The Invoke function calls a method GetServiceInstance on a class ServiceProvider. This returns an instance of a service provider of the given interface type defined in a config file. I'll go into this in another post. So once we've got our concrete instance we can simply use reflection to call the right method. Now have a look at the GenericAttachmentFunction. First it checks that an attachment was attached. Next it de-serializes the parameters to a ServiceParameter object which gives the interface and method that we want to call and the parameters to pass to it. Then we call Invoke as above which returns us an object array as a return value. The return value is then serialized, attached to the response and the function returns.

Tuesday, March 07, 2006

Steve Yegge's whirlwind language tour

This blog by Steve Yegge, a programmer at Amazon, is an excellent diatribe comparing programming languages. No mention of C#, my professional tool (when I'm not forced to use VB.NET - ugh), but I guess a lot of the comments about Java also apply. I'm inspired to have another look at Ruby after reading it.

Wednesday, March 01, 2006

A nice pattern for databinding domain objects

I'm currently working on a windows form application. We've tried to follow good OO principles in designing our business domain classes; encapsulating the business rules, only allowing valid instances of a domain class to be created. We also really like windows form databinding. It's so much easier than writing lots of code to shunt your object's properties back and forth to the form's controls. So, what's the problem. Well, say we have a person class with a name property. The business rules say that the name property can't be an empty string, but how do we create a new person if we can't first bind a blank name to a text box? I've got a nice solution with the use of the 'Builder' pattern. Each domain class has a nested Builder class that is bound to the form instead of an instance of the class itself. The builder allows empty values for required properties so that initially the form has blank fields to be filled in. When the user clicks 'OK', the builder's CreatePerson method is called that returns an instance of the domain class. Because the builder is a nested type it has access to private shared methods that can contain business logic in the domain class, this enforces encapsulation. Validation of the entered fields can be done on the CreateInstance method. Here's the Person class with the nested Builder. Note that the DateOfBirth and Age properties are related via a business rule and that the business rule is also used in the Builder:
Public Class Person

    Private m_name As String
    Private m_age As Integer
    Private m_dateOfBirth As DateTime

    Public Sub New(ByVal name As String, ByVal dateOfBirth As DateTime)
        Me.Name = name
        Me.DateOfBirth = dateOfBirth
    End Sub

    Public Property Name() As String
        Get
            Return m_name
        End Get
        Set(ByVal Value As String)
            If Value = String.Empty Then
                Throw New ValidationException("Name cannot be an empty string")
            End If
            m_name = Value
        End Set
    End Property

    Public Property DateOfBirth() As DateTime
        Get
            Return m_dateOfBirth
        End Get
        Set(ByVal Value As DateTime)
            m_dateOfBirth = Value
            m_age = CalculateAgeFromDob(m_dateOfBirth)
        End Set
    End Property

    Private Shared Function CalculateAgeFromDob(ByVal dateOfBirth As DateTime) As Integer
        Return ((DateTime.Now().Subtract(dateOfBirth).TotalDays) / 360) - 1
    End Function

    Private Shared Function CalculateDobFromAge(ByVal age As Integer, ByVal dateOfBirth As DateTime) As DateTime
        Return New DateTime(DateTime.Now.Year - age, dateOfBirth.Month, dateOfBirth.Day)
    End Function

    Public Property Age() As Integer
        Get
            Return m_age
        End Get
        Set(ByVal Value As Integer)
            m_age = Value
            m_dateOfBirth = CalculateDobFromAge(m_age, m_dateOfBirth)
        End Set
    End Property

    Public Overrides Function ToString() As String
        Return m_name & " " & m_age.ToString() & " " & m_dateOfBirth.ToShortDateString()
    End Function

    Public Class Builder

        Private m_name As String
        Private m_age As Integer
        Private m_dateOfBirth As DateTime

        Public Sub New()
            '
            ' initialise to starting values
            '
            m_name = ""
            m_age = 0
            m_dateOfBirth = DateTime.Now
        End Sub

        Public Function GetPerson() As Person
            Return New Person(m_name, m_dateOfBirth)
        End Function

        Public Property Name() As String
            Get
                Return m_name
            End Get
            Set(ByVal Value As String)
                m_name = Value
            End Set
        End Property

        Public Property DateOfBirth() As DateTime
            Get
                Return m_dateOfBirth
            End Get
            Set(ByVal Value As DateTime)
                m_dateOfBirth = Value
                m_age = CalculateAgeFromDob(m_dateOfBirth)
            End Set
        End Property

        Public Property Age() As Integer
            Get
                Return m_age
            End Get
            Set(ByVal Value As Integer)
                m_age = Value
                m_dateOfBirth = CalculateDobFromAge(m_age, m_dateOfBirth)
            End Set
        End Property

    End Class

End Class

And here's the interesting bits of the form...
Public Class MainForm
    Inherits System.Windows.Forms.Form

    Private m_person As Person
    Private m_personBuilder As Person.Builder

    Private Sub BindPerson(ByVal person As Person)

        m_person = person
        Me.m_nameTextBox.DataBindings.Add("Text", m_person, "Name")
        Me.m_dateOfBirthTextBox.DataBindings.Add("Text", m_person, "DateOfBirth")
        Me.m_ageTextBox.DataBindings.Add("Text", m_person, "Age")

    End Sub

    Private Sub BindPersonBuilder(ByVal personBuilder As Person.Builder)

        m_personBuilder = personBuilder
        Me.m_nameTextBox.DataBindings.Add("Text", m_personBuilder, "Name")
        Me.m_dateOfBirthTextBox.DataBindings.Add("Text", m_personBuilder, "DateOfBirth")
        Me.m_ageTextBox.DataBindings.Add("Text", m_personBuilder, "Age")

    End Sub

#Region " Windows Form Designer generated code "

    Public Sub New(ByVal personBuilder As Person.Builder)
        MyBase.New()
        InitializeComponent()
        BindPersonBuilder(personBuilder)
    End Sub

    Public Sub New(ByVal person As Person)
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call
        BindPerson(person)
    End Sub

    ....

#End Region

    Private Sub m_showPersonButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles m_showPersonButton.Click

        Try
            m_person = m_personBuilder.GetPerson()
            MessageBox.Show(m_person.ToString())
        Catch ex As ValidationException
            MessageBox.Show(ex.Message)
        End Try

    End Sub

End Class

Wednesday, February 15, 2006

A neat way of getting around some OR mapping issues with temporary Domain classes.

I recently discovered a nice pattern for dealing with self joins, and many-to-many joins in a Data Access Layer. Here's the problem. In your domain design you have an class with a collection of itself. This is a common pattern for tree structures.
class MyEntity
{
   MyEntityCollection _myEntities;

   MyEntityCollection MyEntities
   {
       get{ retrun _myEntities; }
   }
}
To do a similar thing in a relational database one would normally have two tables, the entity table and a table to map the relationships between the entities.
create table MyEntity(
   id int
)

create table MyEntityMap(
   from_id int,
   to_id int
)
Now, how do we recreate object map in memory from the relational data. My solution is to use a temporary domain class to represent the map table. It has a reference to the entities on both ends of the relationship and a method to wire them up.
class MyEntityMap
{
   MyEntity _fromEntity;
   MyEntity _toEntity;

   public void WireUp()
   {
       _fromEntity.MyEntities.Add(_toEntity);
   }
}
The MyEntityMapCollection class has a method WireUp() that calls WireUp() on every MyEntityMap object.
class MyEntityMapCollection
{
   ArrayList _myEntityList = new ArrayList();

   ...

   public void WireUp()
   {
      foreach(MyEntity myEntity in _myEntityList)
      {
         myEntity.WireUp();
      }
   }
}
Then in our data access layer we can simply get all the MyEntity objects from the database, storing pointers to them in an object store, then get all the MyEntityMap objects. Each MyEntityMap object gets the pointers for the correct objects from the object store. The last task is to call WireUp() on the MyEntityMapCollection. Here's the DAL code.
public MyEntity GetEntityMap()
{
   MyEntityCollection myEntities = SelectAllEntitiesStoredProcedureWrapper();
   MyEntityMapCollection myMap = SelectAllEntityMapStoredProcedureWrapper();
   myMap.WireUp();

   // some method to get the root MyEntity
   return GetRootEntity(myEntities);
}

Friday, February 03, 2006

Seriously hard core debugging

This is a great blog: If broken it is, fix it you should. She goes really deep into dot net debugging tools and there's some great advice about common application problems. One of my favorite bug bears is ASP Session State. I've seen quite a few applications that fall into the trap of overusing the Session State and then having huge issues with scalability. One such was a mission critical £100 million billing and provisioning system for one of the worlds top cable companies. The original developers had put everything in session state and it simply wouldn't scale. This was classic ASP so they ended up putting an expensive cisco sticky router in front of it, but even that didn't solve all the problems. I remember having a lengthy discussion at the time with someone who said that all these problems will be solved with ASP.NET because you can move the session state handling off to SQL Server. My point was that if you're writing an enterprise application with a SQL server back end, why not maintain your application state by restoring your domain model directly via your Data acces layer on each page hit. That way you'll have real control over state and you wont hit all the scalability issues that Tess goes into in her blog.

Wednesday, February 01, 2006

A VS Macro to count projects classes and functions

I was playing around writing Macros for VS 2003 recently, exploring the CodeModel API and came up with this little solution profiler that counts the number of projects, classes and functions in the current solution. Microsoft has a load of VS 2005 automation samples you can download here
Option Explicit On 

Imports EnvDTE
Imports System.Diagnostics

Public Module SolutionProfiler

    Private Const newline = vbLf
    Private m_textDocument As TextDocument
    Private m_editPoint As EditPoint
    Private m_indent As Integer = 0
    Private m_writeOn As Boolean = False

    Private m_numberOfProjects As Integer = 0
    Private m_numberOfClasses As Integer = 0
    Private m_numberOfFunctions As Integer = 0
    Private m_linesOfCode As Integer = 0

    Public Sub ProfileSolution()

        DTE.ItemOperations.NewFile("General\Text File")
        m_textDocument = DTE.ActiveDocument.Object("TextDocument")
        m_editPoint = m_textDocument.StartPoint.CreateEditPoint()

        Dim solution As Solution = DTE.Solution

        For Each project As Project In solution.Projects
            WriteLine(project.Name)
            WriteProject(project)
        Next

        WriteSummary()

    End Sub

    Private Sub WriteSummary()

        m_writeOn = True
        WriteLine(String.Format("Number of Projects  = {0}", m_numberOfProjects))
        WriteLine(String.Format("Number of Classes   = {0}", m_numberOfClasses))
        WriteLine(String.Format("Number of Functions = {0}", m_numberOfFunctions))
        WriteLine(String.Format("Number of LOK       = {0}", m_linesOfCode))

    End Sub

    Private Sub WriteProject(ByVal project As Project)

        m_numberOfProjects += 1

        Dim codeModel As CodeModel = project.CodeModel
        If Not codeModel Is Nothing Then
            TabIn()
            For Each childElement As CodeElement In codeModel.CodeElements
                If TypeOf childElement Is CodeClass Then
                    WriteClass(childElement)
                End If
                If TypeOf childElement Is CodeEnum Then
                    WriteLine("Enum: " & childElement.FullName)
                End If
                If TypeOf childElement Is CodeInterface Then
                    WriteLine("Interface: " & childElement.FullName)
                End If
            Next
            TabOut()
        End If

    End Sub

    Private Sub WriteClass(ByVal codeClass As CodeClass)

        If codeClass Is Nothing Then
            Return
        End If

        m_numberOfClasses += 1

        WriteLine("Class: " & codeClass.FullName)

        TabIn()
        WriteLine("Properties")
        TabIn()
        For Each member As CodeElement In codeClass.Members
            If TypeOf member Is CodeProperty Then
                WriteProperties(member)
            End If
        Next
        TabOut()
        WriteLine("Functions")
        TabIn()
        For Each member As CodeElement In codeClass.Members
            If TypeOf member Is CodeFunction Then
                WriteFunction(member)
            End If
        Next
        TabOut()
        TabOut()

    End Sub

    Private Sub WriteProperties(ByVal codeProperty As CodeProperty)

        If codeProperty Is Nothing Then
            Return
        End If

        WriteLine(codeProperty.Name)

    End Sub

    Private Sub WriteFunction(ByVal codeFunction As CodeFunction)

        If codeFunction Is Nothing Then
            Return
        End If

        m_numberOfFunctions += 1
        WriteLine(codeFunction.Name)

        Dim startPoint As TextPoint = codeFunction.StartPoint
        Dim endPoint As TextPoint = codeFunction.EndPoint

        Dim lines As Integer = endPoint.Line - startPoint.Line
        m_linesOfCode += lines

    End Sub

    Private Sub TabIn()
        m_indent += 1
    End Sub

    Private Sub TabOut()
        m_indent -= 1
    End Sub

    Private Sub WriteLine(ByVal line As String)

        If m_writeOn Then
            m_editPoint.Insert(New String(vbTab, m_indent) & line & newline)
        End If

    End Sub

End Module