2009-05-23

Generation gap

I have just read this blog by Martin Fowler. I like a lot of what Martin writes, but this is not one of them.

The article suggests using inheritance to separate code-generated classes and manually written classes. You would descend from a code-generated class instead of trying to modify the code-generated source.

The problems I see with this are

Attributes


If I have a descendant of a code-generated class how do I attach .NET attributes to members? I’d have to override them and add the attribute in the descendant which would mean all of my members need to be virtual. Obviously this wouldn’t work for Private members.

I expect you’d have to attach all of your attributes in the tool which generates the source code, but I don’t like this idea. I will explain why later.

Sealed / final


What if we want to create a sealed/final class? We can’t. What if we want a specific member to be sealed/final? Then we couldn’t add attributes to the members.

Partial


Microsoft introduced Partial classes/methods to try to get around the problem of mixing code-generated and manually written code.

public class Person
{
  protected partial void BeforeSetName(ref string newValue);

  private string name;
  public string Name
  {  
    get { return name; }
    set
    {
      BeforeSetName(ref value);
      name = value;
    }
  }
}


The manually written part of the class can now optionally implement BeforeSetName

  public partial void BeforeSetName(ref string newValue)
  {
    if (newValue == null)
      newValue = "";
  }


and to decorate items with meta-data....

[MetadataClass(typeof(PersonMetaData))]
public class Person
{
  ...
}

public class PersonMetaData
{
  [SomeAttributeINeed]
  public string Name { get; set; }
}



I think the partial methods idea is a good one, but meta-data classes? Yuck! They violate the DRY principle, whenever I change the real Person class I have to update the PersonMetaData class too.

Solution


So, what is the solution? How about this?
Code generation tools should be good at what they do!

When I specified the code-generation for ECO MOdeler there were no partial classes/methods, everything had to be generated to a single file which had to mix manually written and auto-generated code together...


  //User code here
  #region ECO Modeler generated
    ...
  #endregion
  //User code here


The code-generator would only rip out and replace code within those regions, leaving the user written code intact. Now at the time partial classes didn’t exist so if I had the chance I would now most likely use BeforeX AfterX type partial methods instead of using *some* regions, but in places I still think they would be necessary.

For example I think that some attributes are quite valid to be entered into your code-generator and some aren’t. In such a case I would expect to see something like this

  #region auto-generated
  [SomeAttributeFromCodeGeneration]
  #endregion
  [SomeManuallyAddedAttribute]
  public string Name { get; set; }


One example of what is or is not relevant to your code-generator is interface realisation. The modeller I was using defined the business classes, from the point of view of the people within the business. Now if one of those classes needs to implement IComparable<T> this is an implementation detail and doesn’t belong in the business model (from which you generate source code) because it will just muddy the information presented to non programmers. ECO Modeler handled this nicely.

Step 1: Code is generated

public class Person : ICloneable, IRoleHolder
{
  ...
}


Step 2: Programmer implements IComparable<T>

public class Person : ICloneable, IComparable<Person>, IRoleHolder
{
  ...
}


Step 3: Model is changed so that Person realises the IStockHolder interface and removes the ICloneable interface

public class Person : IComparable<Person>, IRoleHolder, IStockHolder
{
  ...
}


ECO Modeler did this by storing information about which interfaces it added to the class last time it generated code. If they are no longer in the model they need to come out, any new ones in the model need to be added, and anything else needs to be left alone.

My point here really is this. Why should we be writing programming patterns which do not solve the problem at hand? Why develop programming patterns around holes in our technology? If it is causing us problems then our technology should improve! Code generators should be capable of parsing source code as well as generating it. They should be able to work in their own code arena without messing up ours!

So on this one I disagree, the problem lies in the tools and should be addressed there, not addressed in the coding of our applications.

No comments: