Wednesday, November 14, 2007

The sinking ship

An interesting quote from a colleague today (paraphrased):

Moving developers to projects on the legacy system is like rearranging chairs on the Titanic.

We can tidy it up, but it's still going to the bottom of the Atlantic.  Probably better just to catch the plane.

It came up in the context of investing strategically versus buying tactically.  It's not an either-or question, but it's important to be thinking about long-term goals and assigning resources appropriately to match your strategic view.  The Technical Debt metaphor can offer guidance here too, as buying tactically tends to max out your credit cards (so to speak), while investing strategically actually nets returns.

Monday, November 12, 2007

Coding without confidence

As a favor to my wife, I developed an Excel application to help her employer out with scheduling and status in a small wood manufacturing shop.  Excel was chosen as it was the lowest barrier to entry, and the quickest to get out the door.  It wasn't a mission-critical app and I wasn't getting paid or anything, so I thought I could get something delivered fairly easily.

After all, what was needed was "no big deal" and "we just need something that works".

The final solution used a lot of hidden columns to calculate intermediate results, and macros to make it easier for the end user to interact with the spreadsheet.  The problem is that there are absolutely zero tests on anything, nor can their ever be.  There's no such thing as "ExcelUnit".  I found a "VBAUnit", but it was last released over 4 years ago.

For a while, I couldn't figure out why I was so worried about releasing (i.e., emailing them a copy) the Excel app.  I realized that I had been coding without any confidence that what I was delivering worked, and would continue to work.

I've been so accustomed to having tests and automated builds as my backup, coding without them felt dirty and a little scary.  I was asked "so does it work?" and my only answer was "I hope so".  I was entrenched in "CyfDD", or "Cross-your-fingers Driven Development".  Does it work?  I dunno, better cross your fingers.  CyfDD is a close cousin of "GoykapDD", or "Get-on-your-knees-and-pray Driven Development".  Releasing only once per year or two leads to GoykapDD.

Sometimes I get asked why I bother with automated tests and builds.  Frankly, without automated tests and builds, I have zero confidence in my code.  Nor should I, or should anyone else for that matter.  Confidence in code written without automated tests and builds is just delusion.

Not a good sign

At the end of lunch today at a Chinese restaurant, I cracked open the obligatory fortune cookie to learn what a stale, shrink-wrapped, mass-produced dessert could portend.

It was empty.

I ask for insight into my fate, and the gods answer in their infinite wisdom, "Mu".

Coming after a big launch at work, I really didn't know what to think about this one.  Can't I just get one cookie tell me "Your launch will be great success.  And the Rockets will win tonight."  Is that really too much to ask?

Wednesday, November 7, 2007

Some NAnt tips

Jason got me thinking on some other NAnt tips from his post on listing the targets in the build file.

Include NAnt in your source control

I like to put NAnt in source control and NEVER install it.  I've had too many problems upgrading NAnt and having projects break because of whatever reason, and I have even more issues with custom tasks I write.  It helps to keep your dev machine clean, your build machine clean, and leads to faster dev machine setup time.  Here's my current favorite source tree structure:

  • <ProjectName/Version/Branch>
    • src
      • Where source code goes
    • lib
      • any Dlls referenced from projects, including NUnit dll's
    • tools
      • NAnt
      • NUnit
      • NCover
      • any other exe needed for build/development
    • docs
    • project.sln
    • project.build
    • go.bat

This was all inspired by (stolen from) TreeSurgeon.

Put your build file in your solution

I like to create a solution folder to hold extra project-related files, like my build scripts.  When they're in the solution, it makes it easier to load them up and edit them.  Since ReSharper supports many features for NAnt and MSBuild scripts, it makes sense to have them easy to get to.

I already talked about this earlier, but solution folders are a great way to create logical organization in your solution that doesn't have to correspond to the physical organization on disk.

Create a NAnt bootstrap batch file

I can't stand typing in "nant -buildfile:blahblahblah", especially when I execute the NAnt version that's in my source tree (not the one installed).  I like to create a one-line batch file called "go.bat" that bootstraps NAnt and my build file:

@tools\nant\NAnt.exe -buildfile:NBehave.build %*

The @ symbol tells cmd.exe not to echo the command text out, then I specify the location of NAnt and the build file.  The last part, "%*", just tells cmd.exe to append all commands after "go.bat" to the "nant.exe" program.  For example, I can now do this:

go clean test deploy

The "clean test deploy" commands are targets in my NAnt build script.  Any other NAnt command line options can be typed in after "go".  I put this batch file at the project root level.

Use the "description" attribute to document public targets

As Jason mentioned, the "-projecthelp" NAnt switch will output a list of targets in your NAnt script.  By providing a description in your target, you can provide some better information in the help output.  Here's what NBehave's help looks like:

The targets are separated into Main and Sub Targets.  Main Targets are targets with descriptions, and Sub Targets are those without.  I can still provide internal documentation with XML comments in the build script.  By only providing descriptions on targets that I need to call often, I can create some level of encapsulation in my build file.

I'm sure there are a dozen better tips out there, but these are the ones I make sure to use on every project I'm involved with.

Tuesday, November 6, 2007

Joining Los Techies

How very rude of me, looking back I forgot to mention I've joined Los Techies.  A few things drew me to joining:

  • Name sounds very similar to a certain cerveza
  • The promise of a free T-Shirt
  • Being part of a community is important to me, and the shadier the better
  • My continuing quest to lower the bar wherever I go
  • Peer intimidation factor lowered by lack of MVPs, unlike other communities

My feed URL will stay the same (http://feeds.feedburner.com/GrabBagOfT), and I'll cross-post between the two sites as Google seems to like the Blogspot site. I'll continue to respond to comments on each as well.  However, I'd recommend additionally subscribing to the community feed (http://www.lostechies.com/blogs/MainFeed.aspx).  You'll have access to a greater breadth and depth of content than I could ever hope to create.

Guidance over governance

Being part of a large IT organization, I've become acutely aware of the need to provide a central policy or governance over critical IT operations.  While greenfield/product teams can largely operate in independence, the risk associated with large, complex, and business-critical systems demands some degree of oversight.  However, an IT governance team can fall into some huge traps in their effort to set and enforce policy:

  • Overstepping their bounds and enforcing governance where only guidance is needed
  • Failing to measure the outcomes of governance decisions

For governance bodies to be effective, there needs to be accountability for decisions made.  If teams feel negatively affected by governance decisions, there needs to be a feedback loop back into the decision-making process.  Strong leadership needs to be in place to ensure fact-based decisions and not personal, political, or selfishly reasoned decisions.  As governance has so many opportunities to fail, it becomes even more important to harvest policies internally, grow them from observed best-practices, and encourage adoption through prescriptive guidance.

Since how a team delivers is at least as important as what a team delivers, how can IT influence the "how"?  There are basically two approaches:

  • Guidance
  • Governance

From the team's perspective, the only difference between the two is where the decision gets made to incorporate a policy.  With guidance, the team makes the decision, and with governance, someone else does.  This not-so-subtle difference should be a top consideration when IT decides to enforce policy through governance or encourage policy through guidance.

Removing decisions

One of the key ingredients in self-organizing teams is putting the trust in the team to make decisions in how and what to deliver.  Attempts to remove any abilities to make decisions can quickly have negative impacts to the team unless care is taken to get buy-in from the team.

The more decisions are removed from the team, the more the team feels like an un-creative cog in a machine or a code monkey.  Strong teams thrive on the opportunity to be creative, and teams without creative opportunities become lethargic, disinterested, and largely brain-dead.  It's an enormous waste of resources not to harvest talent available, so the bar needs to be exceedingly high when removing decisions from the team.

It's possible to allow team decisions to be made concerning policy by choosing the scope of the policy.  For example, instead of mandating TDD, require that features have automated tests, then provide guidance on how to do that.  Teams can then decide on specifics.

Favoring guidance

Policies are more effective if they represent a set of core values and principles.  Certain meta-principles are appropriate for governance, such as "automated builds" or "continuous integration".  Once IT governance crosses the line into specifics, that's when decisions are removed from teams and problems arise.

Instead, IT governance bodies can offer guidance on specifics and even support.  They can provide guides to help teams get started, pointing teams in the right direction.  Some teams may have enough internal experience to feed back their experiences back into the governance team, providing even more collective knowledge that the entire organization can take advantage of.

As the saying goes, "people are our most important asset", so if accountability is held for a team, it needs to be able to make decisions that ensure success.  If the team is accountable for decisions they didn't make, morale suffers accordingly.  By favoring guidance, the governance team can focus more on growing appropriate policies by measuring the impact and success of their guidance.

Friday, November 2, 2007

Record and playback state in ASP.NET

Most ASP.NET applications hold various state objects in Session, Application, or other stateful mediums.  For regression testing and defect reproducing purposes, often I want to capture the exact state of these objects and replay them back at a later time, without needing to walk through the exact steps needed to setup that state.  For many defects, I don't know how the state got to where it was, I just know what the state is, so I need a mechanism to record it and play it back.

I can record and playback the state by first capturing the state by downloading a serialized version of the object, and then play it back later by uploading the serialized object file and resuming the application.

Recording state

The basic principle for recording state is that I'd like to serialize one of my state objects and allow the client to download the serialized object as a file.  With direct access to the output stream of the HttpResponse, this turns out to be fairly trivial.

Setting up the state

First, we need some sort of state object.  I'm not too creative, so here's a Cart and Item object, optimized for XML serialization:

public class Item
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Quantity { get; set; }
    public decimal Price { get; set; }
}

public class Cart 
{
    public Guid Id { get; set; }
    public string CustomerName { get; set; }

    [XmlArray(ElementName = "Items"), XmlArrayItem(ElementName = "Item")]
    public Item[] Items { get; set; }
}

Normally I don't put read/write arrays, but to demonstrate the easiest possible serialization scenario, that will do.  For more difficult scenarios, I usually implement IXmlSerializable or provide a separate DTO class strictly for serialization concerns.

In my Default.aspx page, I'll provide a simple button to download the cart, and some code in the code-behind to create a dummy cart and respond to the button click event:

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        Cart cart = new Cart();

        cart.Id = Guid.NewGuid();
        cart.CustomerName = "Bob Sacamano";
       
        Item item = new Item();
        item.Id = Guid.NewGuid();
        item.Name = "Toothpicks";
        item.Price = 100.50m;
        item.Quantity = 99;

        Item item2 = new Item();
        item2.Id = Guid.NewGuid();
        item2.Name = "Ceramic mug";
        item2.Price = 5.49m;
        item2.Quantity = 30;

        cart.Items = new Item[] { item, item2 };

        Session["Cart"] = cart;
    }

    protected void btnDownloadCart_Click(object sender, EventArgs e)
    {
        Response.Redirect("~/download.aspx");
    }
}

Now that I have a Cart object, I need to fill in the "download.aspx" part to allow downloads of the cart.

Outputting state to HttpResponse

The HttpResponse object has a couple of ways to access the raw byte stream sent to the client.  The first is the OutputStream property, which gives me access to a raw Stream object.  Streams are used by a multitude of objects to write raw bytes, such as serializers, etc.

I don't normally use it when I want the end-user to save the file.  If I'm creating a dynamic image or such, I can go directly against the OutputStream, but an alternate way is to use the HttpResponse.BinaryWrite method.  This will allow me to buffer content as needed, and more importantly, provide buffer size information to the end user.  That's especially important for the various browser "download file" dialogs.

Here's my Page_Load event handler for the "download.aspx" page:

   1:  protected void Page_Load(object sender, EventArgs e)
   2:  {
   3:      Cart cart = Session["Cart"] as Cart;
   4:   
   5:      if (cart == null)
   6:      {
   7:          Response.Write("cart is null");
   8:          Response.End();
   9:          return;
  10:      }
  11:   
  12:      try
  13:      {
  14:          MemoryStream ms = new MemoryStream();
  15:          XmlSerializer xmlSerializer = new XmlSerializer(typeof(Cart));
  16:   
  17:          XmlTextWriter outputXML = new XmlTextWriter(ms, Encoding.UTF8);
  18:          xmlSerializer.Serialize(outputXML, cart);
  19:          outputXML.Flush();
  20:          ms.Seek(0, SeekOrigin.Begin);
  21:   
  22:          byte[] output = ms.ToArray();
  23:          outputXML.Close();
  24:          ms.Close();
  25:   
  26:          Response.ContentType = "application-octetstream";
  27:          Response.Cache.SetCacheability(HttpCacheability.NoCache);
  28:          Response.AppendHeader("Content-Length", output.Length.ToString());
  29:          Response.AppendHeader("Content-Disposition", "attachment; filename=cart.xml; size=" + output.Length);
  30:          Response.AppendHeader("pragma", "no-cache");
  31:          Response.BinaryWrite(output);
  32:          Response.Flush();
  33:      }
  34:      catch (Exception ex)
  35:      {
  36:          Response.Clear();
  37:          Response.ContentType = "text/html";
  38:          Response.Write(ex.ToString());
  39:      }
  40:  }

In the first section through line 10, I retrieve the state object (cart) from Session and make sure it actually exists.  If it doesn't, I'll just send an error message back to the client.

In lines 14 through 24, I create the XmlSerializer to serialize the "cart" variable, and call Serialize to serialize the cart out to the MemoryStream object.  MemoryStream provides an in-memory Stream object that doesn't require you to write to files, etc. for stream actions.  Instead, it basically uses an internal byte array to store stream data.

I could have passed the HttpResponse.OutputStream directly to the XmlTextWriter's constructor, but I would lose the ability to buffer or give size information back to the client, so I just dump it all into a byte array.

Finally, in lines 26 through 32, I set up the Response object appropriately and call BinaryWrite to write the raw byte array out to the client.  I disable caching as I want the client to bypass any server or client cache and always download the latest Cart.  Also, I set the appropriate headers to direct the client to download instead of just view the XML.  If I didn't include the "Content-Disposition" header, most likely the client would simply view the XML in their browser.  That's done through the value "attachment".

I can also control what filename gets created on the client side with the "filename" value in the "Content-Disposition" header.  Without that, a dummy or blank filename would be shown.  Here's what it looks like after I click the "Download cart" button:

Notice that both the filename and file size are populated correctly.  I can save this serialized Cart object anywhere I like now as a perfect snapshot of our state object, ready to be played back.  Also, the "cart.xml" file doesn't exist anywhere on the server.  It only exists in server memory and is streamed directly to the client.

Playing it back

Now that I have a page that allows me to download my Cart from Session, I need a page where I can upload it back up and continue through the application.  This can be accomplished through the FileUpload control and some de-serialization magic.

Uploading the saved state

First, I'll need to add a couple of controls to my "Replay.aspx" page to handle the file upload and to initiate the replay:

<asp:fileupload id="fuCart" runat="server" />
<br />
<asp:button id="btnReplay" onclick="btnReplay_Click" runat="Server" text="Replay" />

In the click handler, I need to read the file from the FileUpload control and deserialize that into a Cart object:

protected void btnReplay_Click(object sender, EventArgs e)
{
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(Cart));
    Cart cart = (Cart)xmlSerializer.Deserialize(fuCart.PostedFile.InputStream);
    Session["Cart"] = cart;

    Response.Redirect("~/ShoppingCart.aspx");
}

Once the cart is downloaded and deserialized, I set the Session variable to the new cart instance and proceed directly to the ShoppingCart.aspx page.  I can verify that my Cart object has the exact same state as the one I saved earlier by examining it in the debugger:

I can see all of the IDs are correct, the number of items are correct, and all of the data values match the original cart saved out.  When I proceed to the cart page, my state will be exactly where it was when I originally downloaded the cart.

Conclusion

If one of our QA people encountered a defect, they can now save off their state and attach it to our defect tracking system, allowing us to replay exactly what their state was and reproduce the defect.  Record/playback is also perfect for regression test suites, which sometimes need to rely on specific inputs (instead of workflows) to reproduce defects.  Combining this ability with WatiN or Selenium can give me a very powerful testing tool.

When defects are found, we can download the state of the user when replaying their workflow is difficult, impossible, or we just don't know what happened.  Through replaying a user's exact state, we can quickly determine root causes for failures and create regression tests to ensure the defects do not surface again.

Just make sure you take care to secure the "record" and "playback" pages appropriately.  You probably won't want that available to customers.