DaedTech

Stories about Software

By

Customizing Generated Method Header Comments

Editorial note: I originally wrote this post for the SubMain blog.  You can check out the original here, at their site.  While you’re there, have a look at GhostDoc, the subject of this post.

Last month, I wrote a post introducing you to T4 templates.  Near the end, I included a mention of GhostDoc’s use of T4 templates in automatically generating code comments.  Today, I’d like to expand on that.

To recap very briefly, recall that Ghost Doc allows you to generate things like method header comments.  I recommend that, in most cases, you let it do its thing.  It does a good job.  But sometimes, you might have occasion to want to tweak the result.  And you can do that by making use of T4 Templates.

Documenting Chess TDD

To demonstrate, let’s revisit my trusty toy code base, Chess TDD.  Because I put this code together for instructional purposes and not to release as a product, it has no method header comments for Intellisense’s benefit.  This makes it the perfect candidate for a demonstration.

If I had released this as a library, I’d have started the documentation with the Board class.  Most of the client interaction would happen via Board, so let’s document that.  It offers you a constructor and a bunch of semantics around placing and moving pieces.  Let’s document the conceptually simple “MovePiece” method.

public void MovePiece(BoardCoordinate origin, BoardCoordinate destination)
{
    VerifyCoordinatesOrThrow(origin, destination);

    var pieceToMove = GetPiece(origin);
    AddPiece(pieceToMove, destination);
    RemovePiece(origin);
    pieceToMove.HasMoved = true;

    ReconcileEnPassant(origin, destination, pieceToMove);
}

To add documentation to this method, I simply right click it and, from the GhostDoc context menu, select “Document This.”  Alternatively, I can use the keyboard shortcut Ctrl-Shift-D.  Either option yields the following result.

/// <summary>
/// Moves the piece.
/// </summary>
/// <param name="origin">The origin.</param>
/// <param name="destination">The destination.</param>
public void MovePiece(BoardCoordinate origin, BoardCoordinate destination)
{
    VerifyCoordinatesOrThrow(origin, destination);

    var pieceToMove = GetPiece(origin);
    AddPiece(pieceToMove, destination);
    RemovePiece(origin);
    pieceToMove.HasMoved = true;

    ReconcileEnPassant(origin, destination, pieceToMove);
}

Let’s Make a Tiny Tweak

Alright, much better!  If I scrutinize the comment, I can imagine what an intellisense-using client will see.  My parameter naming makes this conceptually simple to understand, so the intellisense will tell the user that the first parameter represents the origin square and the second parameter the destination.

But let’s say that as I look at this, I find myself wanting to pick at a nit.  I don’t care for the summary taking up three lines — I want to condense it to one.  How might I do that?

Well, let’s crack open the T4 template for generating a method header.  Recall that you do this in Visual Studio by selecting Tools->Ghost Doc->Options, and picking “Rules” from the options pane.

If you double click on “Method Template”, as highlighted above, you will see an “Edit Rule” Window.  The first few lines of code in that window look like this.

<#@ template language="C#" #>
<#  CodeElement codeElement = Context.CurrentCodeElement; #>
/// <summary>
///<# GenerateSummaryText(); #>
/// </summary>
<#	if(codeElement.HasTypeParameters) 
	{
		for(int i = 0; i < codeElement.TypeParameters.Length; i++) 
		{ 
			TypeParameter typeParameter = codeElement.TypeParameters[i]; 
#>

Hmmm.  I cannot count myself an expert in T4 templates, per se, but I think I have an idea.  Let’s put that call to GenerateSummaryText() inline between the summary tags.  Like this.

<#@ template language="C#" #>
<#  CodeElement codeElement = Context.CurrentCodeElement; #>
/// <summary><# GenerateSummaryText(); #></summary>

That should do it, right?  Let’s regenerate the comment and see what it looks like.  This results in the following.

/// <summary>Moves the piece.
/// </summary>
/// <param name="origin">The origin.</param>
/// <param name="destination">The destination.</param>
public void MovePiece(BoardCoordinate origin, BoardCoordinate destination)
{
    VerifyCoordinatesOrThrow(origin, destination);

    var pieceToMove = GetPiece(origin);
    AddPiece(pieceToMove, destination);
    RemovePiece(origin);
    pieceToMove.HasMoved = true;

    ReconcileEnPassant(origin, destination, pieceToMove);
}

Uh, oh.  It made a difference, but somehow we only got halfway there.  Why might that be?

Diving Deeper

To understand, we need to look at the template in a bit more detail.  The template itself has everything on one line, and yet we see a newline in there somehow.  Could GenerateTextSummary cause this, somehow?  Let’s scroll down to look at it.  Since this method has a lot of code, here are the first few lines only.

private void GenerateSummaryText()
{
	if(Context.HasExistingTagText("summary"))
	{
		this.WriteLine(Context.GetExistingTagText("summary"));
	}
	else if(IsAsyncMethod())
	{
		this.WriteLine(Context.ExecMacro("$(MethodName.Words.ExceptLast)") + " as an asynchronous operation.");
	}
	else if(IsMainMethod())
	{
		this.WriteLine("Defines the entry point of the application.");        
	}

Aha!  Notice that we’re calling WriteLine.  What if we did a find and replace to change all of those to just Write?  Let’s try.  (To do more serious operations like this, you will want to copy the text out of the editor and into your favorite text editor in order to get more operations).

Once you have replaced all instances of WriteLine with Write in the template, here is the new result.

/// <summary>Moves the piece.</summary>
/// <param name="origin">The origin.</param>
/// <param name="destination">The destination.</param>
public void MovePiece(BoardCoordinate origin, BoardCoordinate destination)
{
    VerifyCoordinatesOrThrow(origin, destination);

    var pieceToMove = GetPiece(origin);
    AddPiece(pieceToMove, destination);
    RemovePiece(origin);
    pieceToMove.HasMoved = true;

    ReconcileEnPassant(origin, destination, pieceToMove);
}

Success!

Validation

As you play with this, you might have noticed a “validate” button in the rule editor.  Use this liberally!  This button will trigger a parsing of the template and provide you with feedback as to validity.  The last thing you want to do is work in here for many iterations and wind up with no idea what you broke and when.

When working with these templates, think of this as equivalent to compiling.  You wouldn’t want to sit for 20 minutes writing code with no feedback as to whether it builds or not.  So don’t do it with these templates.

The Power at Your Disposal

I’ll wrap here for this particular lesson, but understand that we have barely scratched the surface of what you can do.  In this post, we just changed a bit of the formatting to suit a whim I had.  But you can really dive into ways of reasoning about and documenting the code if you so choose.

Stay tuned for future posts on more advanced tips and tricks with your comment templates.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Phil
6 years ago

Auto-generation of headers and documentation provide business value because you can check off that box that says “we have documentation”. Still, the documentation provided by those generators is only as helpful as the method and class headers. This type of documentation does not provide and useful technical value. In my experience, it odes just the opposite. In your example, the method and parameter names perfectly describe the function and expected input values. In this example, the headers are essentially re-specifying the specification that you’ve already specified. Extra comments and headers and such cause developers to either read more (takes more… Read more »