Posts
48
Comments
146
Trackbacks
0
December 2012 Entries
YAGNI and Professional Code

I’ve heard (and used) YAGNI (You Ain’t Gonna Need It) quite often in my software development career. It’s a battle cry for shipping a minimum viable product and letting the real-world usage dictate what new features and improvements are really needed. Generally speaking I think that this ruthless minimalism is a good thing. I think we’ve all fallen into the “pie in the sky” thinking about adding lots of bells and whistles to whatever feature we’re working on. I for one also know the feeling of spending a lot of time on one aspect of a new feature only to later discover that no one really uses it. I like to think that, over time, I’ve begun to develop some sense of when a given feature is likely to be useful and when I should YAGNI it out of my task list, but then again I also feel like the more I know the less I know. Lately I’m finding that when I’m in doubt it’s best to err on the side of doing less and keeping things as simple as possible.

That said, there is a certain classification of feature that I sometimes regret omitting in the name of YAGNI. I recently read a fantastic post on Oren Eini’s blog titled, “On Professional Code”. I think this quote from that post sums it up quite nicely:

…a professional system is one that can be supported in production easily. About the most unprofessional thing you can say is: “I have no idea what is going on”.

This post really hit home for me because I've recently transitioned into a support role (or devops, if you like) at work. One of the things that I’m now responsible for is assisting our support team with troubleshooting difficult issues and figuring out what’s going on when the phone is ringing off the hook with end-users complaining that the “system is slow” and they can’t get their work done. While it’s a relatively rare occurrence, I absolutely hate having to say, “I don’t know what’s going on”. When there’s an odd error being thrown at a user who is trying to do something in our system it’s terribly frustrating to have to crack open the source code of the system to figure out why it’s being thrown. The error messages that are logged are often loaded with developer-speak or things like, “this shouldn’t happen”. I’m one of the more tenured developers on our team so I know that if I have difficulty understanding why a given error is being raised our support staff has almost zero chance of being able to figure it out on their own. When they can’t figure it out, they have to ask for help and when they have to ask for help me or one of my co-workers has to stop working on something else (usually some improvement to our infrastructure, an internal tool that will make our lives easier, or some performance profiling/tuning) to help them and make sure our customers can get their work done. I feel very comfortable in saying that these kinds of poorly documented and understood error conditions can be tremendously costly to any company. If you have to read the source code of your application to fix an issue that doesn’t require changing the source code (i.e. it’s a bug in the system), then you’re failing to write “professional code” as Oren defines it in that post.

As a developer I know that I’m as guilty as anyone of writing sub-par error handling code and leaving cryptic error messages in the log. I can’t speak for anyone but myself, but I think that this happens for one of two different reasons:

  1. Mistake: Sometimes I simply didn’t think that a particular piece of input would ever end up being passed into that routine that I wrote. I think I’m usually a pretty good practitioner of defensive programming, but sometimes I make a mistake.
  2. YAGNI: Other times I might have made a conscious decision to check for invalid input or assert that data was in the state the I expect it to be in, but I figure that the terse off-the-cuff error message will be adequate for troubleshooting this issue that “shouldn’t really ever happen anyway”. I have more important user-facing aspects of this feature to complete still, so I can surely apply YAGNI to this error message and move on.

As long as software is written by human beings we’ll always make mistakes. I’m not interested in exploring the mitigation of damage caused by human error in this post because that’s a topic that’s been explored in-depth by folks much smarter and more experienced than I am. Instead I’d like to focus on issue #2: that application of YAGNI to error-handling features. I’ve come to the realization lately that there is no such thing as ‘YAGNI’ when it comes to exposing information about what’s going on in a live production application. In fact, I’ve started using another five-letter acronym to describe the development of features like this: YCNHE or “You Can Never Have Enough”. 

In my opinion, the cost-benefit ratio for adding useful information to error handling code is such that you can almost never have enough. Any time I add a message to some code that will end up being logged I ask myself the following question: “Who will read this message and what action will they need to take?”. Almost no one can see a message like, “Unexpected value for parameter InputType” and know what needs to happen to fix it. This is just an informative statement with no imperative command or pointer to additional information. Granted there’s probably a ton of contextual information that was captured along with the message like the timestamp from the server, a stack trace showing what routine or line of code was being executed, and the username of the person that was running the application when the error occurred. That type of contextual information can be very useful to a developer trying to reproduce or fix a defect but it’s nearly useless to a non-developer trying to resolve an issue.

So what can you do to help alleviate this issue? Here are a few thoughts I’ve been formulating and will be experimenting with in the coming months:

  1. Encourage all developers to ask themselves, “Who will read this message and what action will they need to take?” whenever they are logging a message. Asking yourself this question leads to better error messages. Some quick examples of easily added information that can be very useful to non-developer staff:
    1. Does this message represent something that needs to be addressed immediately or is it just some information being captured for future reference?
    2. What is the primary key / identity of the records from the database that were involved in the error?
    3. What were the specific values that were input that caused the overflow/out of range/divide by zero/whatever run time error.
  2. Consider using numeric error codes as a point of reference in all logged errors. Often a logged error message can’t (and shouldn't) contain all of the information that might be needed in order to correct the issue. By using error codes you can easily setup external documentation about each unique error code that can serve as a place to capture additional troubleshooting steps, support staff notes, and all of the other various bits of information that can be useful when troubleshooting an issue in production. Using error codes can also help you establish relative severity in your errors. For example, you could say that codes in the 50000+ range represent potentially fatal errors that need to be sent out to e-mail/pager notifications immediately while stuff in the 10000 range might just need to be logged for future reference if needed.
  3. Create “dry run” and/or “debug” modes for complicated procedures/algorithms. If you can allow support staff to do a “dry run” of a complicated procedure that a customer is trying to do that will create a detailed debug-level log of everything that happened you can let them see how the problem gets broken down into steps by the code and on which step things fell apart. This is the type of detail that developer might need to use an interactive debugger for, but if a particular process in your system is complicated enough that you need to step through it a lot why not let all members of the team (developer or otherwise) get that same step-through experience?
  4. Make it easy for all members of the team to build and access support tools. The people that support your application in production always need new tools. They need a way to un-lock that bit of data for an end user who mistakenly locked it too soon. They need to be able to execute a query to see how many customers make use of a configurable feature. They need to be able to insert new rows into lookup tables that don’t get changed frequently enough to warrant the construction of an user-facing interface. Sometimes it’s faster and easier to simply do these one-off tasks manually on-the-spot than to build a tool but doing things manually simply doesn’t scale as your team and customer base grows. If you can lower the overhead involved with building these kinds of tools then you’re far more likely to actually build them. Command line applications are great for this because you don’t have to get bogged down in the creation of UIs for these tools that will only be used by internal staff.
  5. Have a resource/team dedicated to improving the situation. Our support/devops team is still new and getting ramped up, but I’ve already found it immensely helpful to to be able to improve our error messages and internal documentation on the spot whenever I need to answer a question for a member of our support team. The developers that build customer-facing features won’t always have the foresight to build features in a way that will make them easy to troubleshoot in production so having a team dedicated to that job will help ensure that it gets done.

I think it’s pretty commonly accepted in the software development community that “good” code is easily readable and maintainable by other developers, but I think that this notion needs to be expanded. Good code should also be easily supportable by people who aren’t developers. Next time you write a new feature take some time to think about how that feature might need to be supported in production. Are there things that would be relatively easy to add that might pay big dividends down the road in terms of improving the supportability of the system in production?

Posted On Monday, December 31, 2012 12:28 PM | Comments (0)
Finding Memory Leaks in .NET Compact Framework Applications

This post is a collection of my findings from a recent effort to eliminate memory leaks in a .NET Compact Framework (CF .NET) application. All of the information in this post is available elsewhere on the web but after spending a lot of time pulling it all together for my own purposes I thought I might write up my findings and maybe save someone else a few hours of time down the road. The majority of this post will focus on the initial setup needed to even get to the point that you can start to profile memory usage and find leaks in a running CF .NET application as I found that to be the most confusing and frustrating part of the entire process.

I’ve found that the .NET Compact Framework Remote Performance Monitor tool is the most memory profiling tool available for the purposes of finding memory leaks. Here are a couple of very informative blog posts by Steven Pratschner describing how this tool can be used:

Analyzing Device Application Performance With The .ENT Compact Framework Remote Performance Monitor

Finding Memory Leaks using the .NET CF Remote Performance Monitor

1. Setup A Windows Mobile Emulator Image

If you already use a Windows Mobile emulator image for doing CF .NET development then you should be able to just use that same image to profile memory usage and find leaks. If you don’t currently use an emulator image you’ll need to set one up and get to the point that you can deploy and run your app on the emulator. You might at this point ask why you can’t do this on a physical device. You can do it on a physical device but I’ve opted to use an emulator for a few reasons:

  1. Most physical devices that I have access to run Windows CE and I have not yet been able to get the memory profiling tool I prefer to use to work with Windows CE.
  2. I wanted to make this easy to setup for other members of the development team that I work on so that others will be able to work on memory leak issues in the future.
  3. Generally speaking I’ve found it’s easier to fire up an emulator image and start working than to mess with cradling a physical device.

If you need to setup an emulator image to work with, start out by grabbing some from here: Windows Mobile 6.1.4 Emulator Images. Download the ‘Professional Images’ package from that site and install it. Once installed you should be able to start, cradle, and deploy your application to a running emulator image.

2. Install Hotfix KB 2436709 for .NET Compact Framework 3.5

Once you have an emulator that can run the application you want to profile, you’ll need to install a hotfix to the Compact Framework in order to let the Remote Performance Monitor take snapshots of the garbage collected heap while the application is running. These snapshots are what ultimately let you see what objects are leaking as the application runs. You can request and download this hotfix here: http://support.microsoft.com/kb/2436709

Once downloaded, cradle the emulator and copy the .cab file over to the emulator and run it on the device to install over the existing .NET Compact Framework on the device.

3. Install The .NET Compact Framework Remote Performance Monitor

The Remote Performance Monitor tool comes with the Power Toys for .NET Compact Framework 3.5 package. You can download this package here: http://www.microsoft.com/en-us/download/details.aspx?id=13442

It’s worth nothing here that I also took a look at the CLR Profiler tool which also ships with the Power Toys package. This tool was easier to get setup than the Remote Performance Monitor, but I found it really hard to parse the resulting output for an app as large and complicated as the one I was profiling. Also, taking snapshots with this tool was painfully slow to the point that it wasn’t really practical for me to use. As always, your mileage may vary.

4. Deploy And Profile Your Application

Once you have an emulator image that can run your application and has the properly hot-fixed version of the .NET Compact Framework installed you’re ready to start profiling your application with the remote performance monitor. The blog posts that I linked to at the top of this article have a lot of detailed and helpful information about this, but I’ll summarize it here as well.

First, make sure that your emulator is cradled and that the application you want to profile is available on the emulator. Also be sure that no applications are currently running on the emulator.

Next, start the Remote Performance Monitor and click the green ‘Play’ icon to start and profile an application. This brings up the ‘Launch Application’ dialog:

image

From this dialog you’ll select the device that you want to profile the application on. If you’ve setup an emulator for this you should have an option like ”Pocket_PC (ActiveSync)” in this list. You’ll also need to specifiy the path to the application that you want to profile on the device. If your app lives under the “Program Files” path in a sub folder called “Myapp” you would enter: “\Program Files\MyApp\MyApp.exe”. You can optionally specify a path on the desktop from which to copy the application, but I’ve always found it easier to just have the application already deployed and available on the emulator. Finally, enter any startup arguments that your application might need and then click ‘OK’ to launch and start profiling the app.

5. Take and Compare GC Heap Snapshots

Once your app is running and being profiled the main Performance Monitor Window will start being constantly updated with a wide variety of metrics. There are a lot of interesting metrics here and most of them line up with counters that you can setup and monitor in PerfMon for Windows applications on a desktop or server. For the purposes of finding memory leaks, however, we’re primarily concerned with the ‘GC Heap’ button that will become available in the main toolbar of the Performance Monitor.

Clicking the ‘GC Heap’ button will force a garbage collection on the running application and then take a snapshot of the garbage collected heap. This snapshot lets you see all objects that the garbage collector wasn’t able to clean up. As you use your application and keep taking snapshots you can start to see if certain types of objects keep getting higher and higher instance counts which is a pretty good indication that you have a leak somewhere. The Finding Memory Leaks using the .NET CF Remote Performance Monitor blog post that I linked to at the start of this post has a lot more information and a good example of how to do this.

Posted On Thursday, December 27, 2012 6:26 AM | Comments (1)
C# to VB .NET Conversion Issues

In my last post I discussed the approach we used at my job to convert an ASP .NET web forms application written in VB .NET to C#. In that post I alluded to some of the issues that we encountered during the conversion but didn’t delve into any of the technical details. I decided to take the technical specifics of some of the more interesting conversion issues and make a separate post of them. If you ever embark on a conversion project like ours these might be of use to you.

Option Strict and Option Explicit

This isn’t a conversion issue per se, but if you want to convert a VB .NET project to C# you definitely want to have these compiler settings turned on in your VB .NET project prior to doing so. Turning these compiler flags on forces you to both explicitly declare all variables before using them (Option Explicit) and disallows some of the implicit data type conversions that VB .NET will do for you (Option Strict). Having both of these flags turned on will make the VB .NET code behave more like its C# equivalent with respect to variable declaration and type conversion. That said, if you’ve been developing in VB .NET without these settings on you might not be the type of person that would really want to convert that code to C# anyway.

Implicit Data Type Conversions

Even with Option Strict turned on VB .NET is more permissive with implicit data type conversions than C#. For example, in VB .NET the following code will compile with Option Strict turned on:

   1:   
   2:  'Enumeration for defining columns in a grid
   3:  Public Enum OrderGridColumn
   4:        OrderDate
   5:        OrderedBy
   6:        ShipDate
   7:  End Enum
   8:   
   9:  'When binding data to the grid:
  10:  gridRow.Columns(OrderGridColumn.OrderDate).Text = data.OrderDate.ToString("g", CultureInfo.CurrentCulture)

Here we’re using an enumeration to name the indexes of the columns in a grid for displaying order data. At runtime we’re using these values to access the grid’s columns and do special formatting for the OrderDate field. This is a somewhat trivial example, but we had lots of code like this scattered throughout our VB .NET project. The C# conversion tool converted line 10 of the above snippet to:

gridRow.Columns[OrderGridColumn.OrderDate].Text = data.OrderDate.ToString("g", CultureInfo.CurrentCulture);
Since the indexer on the Columns field of the gridRow takes an integer as a parameter, the C# compiler complains about not being able to implicitly cast the OrderGridColumn value to an int. VB .NET, on the other hand, had no issue with that implicit conversion. Immediately after using the automated conversion tool on our VB .NET project we had tons of C# compiler errors related to this implicit data conversion. Thankfully this was pretty easily fixed with a regular expression and Visual Studio’s find/replace feature to shim in the explicit cast to an int:
gridRow.Columns[(int)OrderGridColumn.OrderDate].Text = data.OrderDate.ToString("g", CultureInfo.CurrentCulture);

For the record, there isn’t much that I like about VB .NET, but being able to implicitly cast enum values to integers is one thing that I miss.

Query-style LINQ expressions and Lambdas

VB .NET supports query-style LINQ expressions just like C# but with slightly different syntax. The automated conversion tool completely whiffed on converting these queries from VB .NET to C#. I ended up re-writing them to use the LINQ extension methods and the converter did better with those but still got choked up on some of the more complicated lambdas. If I had it to over again I would probably have hunted down these bits of VB .NET code with query expressions/heavy lambda use and re-written them to use simple loops prior to the conversion. After the conversion I could have then just let ReSharper do the LINQ refactoring for me.

Explicit Return Statements

In VB .NET you can return a value from a function or property getter by assigning the return value to the name of the property or method, like this:

   1: Public Function Add(ByVal firstNumber As Integer, ByVal secondNumber As Integer) As Integer
   2:   Dim result as Integer = firstNumber + secondNumber
   3:   Add = result
   4:   Exit Function 'You don't technically need the explicit Exit Function in this case
   5: End Function

Obviously C# doesn’t use this convention and requires an explicit return statement for all possible execution paths of a method or property getter. The automated conversion tool we used didn’t always properly convert to explicit return statements so I ended up changing places in VB .NET where we were using this convention to use explicit return statements instead.

When Clauses In Catch Blocks

VB .NET allows the use of ‘When’ clauses when catching exceptions to define multiple catch blocks that are used only when the condition in the When clause is true. C# doesn’t really have an equivalent way of doing this, so you have to pay some special attention to any Catch…When blocks when converting VB .NET to C#. In most cases the same run-time behavior can be achieved in C# by catching the desired exception types and then using switch or if/else statements to branch the handling logic.

For example, if you had the following VB .NET code:

   1:  Try
   2:       'Some code that might throw an exception
   3:  Catch ex As SqlException When ex.ErrorCode = SomeErrorCode
   4:        'handle this error code
   5:  Catch ex As SqlException When ex.ErrorCode = SomeOtherCode
   6:       'handle the other error code
   7:  End Try

The equivalent C# would be:

   1:  try
   2:  {
   3:     //some code that might throw an exception
   4:  }
   5:  catch (SqlException ex)
   6:  {
   7:      if(ex.ErrorCode == SomeErrorCode)
   8:          //handle this error code
   9:     else if (ex.ErrorCode == SomeOtherCode)
  10:         //handle the other error code
  11:     else
  12:       throw;
  13:  }

Note that we need to re-throw the exception in the ‘else’ condition to completely mirror the VB .NET catch blocks, as the exception would not have been caught at all if it didn’t meet one of the error code conditions.

Divide By Zero Errors

The last issue I want to discuss in this post was by far the strangest and least expected of all of the issues that we encountered while reviewing and testing the converted code.

Consider the following snippet of VB .NET code:

   1:  Dim dividend As Integer = 1
   2:  Dim divisor As Integer = 0
   3:  Dim quotient As Double = dividend / divisor
   4:  Console.WriteLine(quotient)

This code will print ‘Infinity’ to the console. Now consider the following equivalent C# code:

   1:  int dividend = 1;
   2:  int divisor = 0;
   3:  double quotient = dividend/divisor;
   4:  Console.WriteLine(quotient);

This code will throw a System.DivideByZeroException. The reason this happens is because of the difference in the way that VB .NET and C# interpret the “/” mathematical operator. In C# the “/” operator performs division that is consistent with the types of the dividend and divisor. In the C# snippet above the dividend and divisor are both typed as integers so the “/” operator is performing integer division and therefore throwing an exception. In VB .NET on the other hand the “/” operator always does division that will return the full quotient including any remainder value in the fractional portion (see http://msdn.microsoft.com/en-us/library/25bswc76(v=vs.80).aspx). When the division operation returns a floating point number it has the flexibility to indicate that the value approaches infinity (even if that’s not technically what the result of that division is).

To help deal with this VB .NET also exposes a “\” operator that will always return an integer result. If we were to re-write the VB .NET snippet above to use the the “\” operator for division instead it would also through a DivideByZeroException. Similarly if we were to tweak the C# code to type the dividend and/or divisor as a ‘double’ it would write ‘Infinity’ to the console. Note that the the ‘Infinity’ value is actually the PositiveInfinity constant exposed by the double data type.

If you’re interested there are a couple of questions on Stackoverflow and the Programmers Stack Exchange site that explore different behaviors for dividing by zero in programming languages:

http://programmers.stackexchange.com/questions/119987/should-integer-divide-by-zero-halt-execution

http://stackoverflow.com/questions/6563788/divide-by-zero-infinite-nan-or-zero-division-error

Posted On Wednesday, December 19, 2012 9:42 PM | Comments (0)
Converting VB .NET to C#: A Post Mortem

For the past few years I’ve been working with a fairly large ASP .NET web application. The app began its life as Classic ASP and was later ported to ASP .NET web forms written in VB .NET. Over the years the language preference of the development team shifted and we started finding ways to leverage C# alongside the existing VB .NET web forms project. This shift lead to a collection of class libraries, Windows services, and assorted utilities written in C# that were built up around the aging VB .NET code base. We found ways to push a lot of code out of the VB .NET web forms project, but still found ourselves having to add or edit code in the VB .NET project often enough to make it painful. In addition, building our main solution containing the VB .NET web forms project and our various C# class libraries took forever, apparently because Visual Studio didn’t like our mixed-language projects. During our first annual DevCon (a yearly week-long meeting of our distributed team) in early 2011 we identified the presence of VB .NET as our primary source of pain on the development team and committed ourselves to eliminating it.

Why?

While the majority of this post will be focused on how the conversion was done I think it’s important to briefly talk about why we did it. Taking a large and stable codebase and changing the language it’s written in might seem foolhardy. At a past job I had explicitly decided against converting a much newer and smaller VB .NET application to C# based solely on the fact that it didn’t add any value for our users. I was working as a contractor at the time and couldn’t tell our client that we wanted to spend 2-3 weeks doing a “re-write” that wasn’t going to add any of the new features that were needed. At my current job, however, we strive to devote a portion of our development time each sprint toward eliminating developer pain. We understand that alleviating this pain may not immediately add value to our product, but it will increase productivity and reduce attrition in the future. Some teams use the term “technical debt” when referring to the painful areas of their codebase and recognize that, just like any debt, it’s most cost effective to pay it off quickly.


You could probably make the argument that programming language choice is not really technical debt, but technical debt or not we knew one thing: everyone hated working with VB .NET and that alone was reason enough to get rid of it.

Our Approach

Our VB .NET web forms project consisted of roughly 300,000 lines of code spread over about 600 different pages (.aspx files). Due to the sheer size of the project we opted to use an automated conversion tool to do the brunt of the work for us. There are a number of VB .NET –> C# converters out there, but we ultimately decided to use the VBConversions VB .NET to C# Converter because it offered the fastest, easiest, and highest quality conversion of any of the tools that we evaluated. That said, no automated conversion tool will produce 100% perfect results for all conversions and no one on the team was comfortable blindly trusting an automated tool, so we also had to manually review every converted file to clean up compiler errors and other incorrectly converted code that we found. The code review was followed by a regression test of the application before finally releasing it to the wild.

Conversion Preparation

While we we identified the elimination of VB .NET as a top technical debt priority at our DevCon in early 2011 we didn’t actually complete the C# conversion project until our DevCon in early 2012. While the project spanned roughly 1 year, it certainly didn’t take a year’s worth of developer hours to complete. Any time spent on the project in the months between the 2011 and 2012 DevCon meetings was spent in small increments (i.e. a few hours here and there) doing preparation work.


The preparation work for this project consisted mostly of identifying potential issues with the VB .NET code that would trip up the converter. The VBConversions tool will generate list of suggested changes for the source VB .NET code that help it perform a more accurate conversion. In this way it encourages you to run a conversion, fix potential issues, and run the conversion again to see the updated results. Our initial pass at converting the VB .NET code yielded a C# project with well over 5,000 compiler errors. By the time we were ready to make the final conversion and begin our manual review process we were down to several hundred compiler errors. If we had taken more time with this step we likely would have ended up with an even cleaner conversion, but I don’t think that we would have ever had a 100% perfect result.


Prior to conversion we froze all other development efforts because we knew that we need to focus solely on the conversion until it was completely finished before trying to pick up any new work. Trying to balance new development work with the conversion and the potential bug fixes that would be needed following the conversion would be too difficult, so we had to get buy-in from our product management to let us tackle this conversion effort while putting other work on-hold for awhile. Once the conversion process began the only changes going into source control were related to the conversion itself.

Conversion

Because the entire development team would be in the same conference room at the same time for a week during our 2012 DevCon meeting we decided to finally pull the trigger and get the conversion done during that week. Over the weekend prior to the meeting I ran the final conversion and committed the converted C# project to source control alongside the VB .NET project. At this point we had code in source control that was known to be broken. This is normally a big no-no, but having everything in source control was going to make collaboration much easier. Also, we left all of the existing VB .NET code and build scripts untouched meaning that all of our CI builds and QA deployment scripts continued to work even though we had a big pile of broken code in source control. We started the week with two Visual Studio solutions that were under the same top level folder in source control:

  • Original Website Solution
    • Core Class Libraries and Services (already written in C#, left untouched during the conversion)
    • Original VB .NET Web Forms project (also left untouched during the conversion)
  • C# Website Solution
    • Core Class Libraries and Services (same projects referenced in the original solution)
    • Converted C# Web Forms project (the converted project that needed to be reviewed)

Having both the original VB .NET and converted C# exist side-by-side in source control allowed us to open both projects for side-by-side comparison during the review process. We were also able to make commits of the cleaned up C# code after each file was reviewed.
We used a shared Google Docs spreadsheet during the conversion to help keep track of our progress. This spreadsheet had the following columns:

  • File Path – Relative path and file name of the VB .NET code file (since the conversion produced a one-to-one mapping of code files we ended up with one row per file to be reviewed)
  • Lines (approx) - Rough count of the lines of code in the VB .NET file (We came up with this count using a simple Powershell script that you can find here: https://gist.github.com/1674457)
  • Reviewer – As developers picked up files to review they would put their names in this column to ensure that we didn’t duplicate our efforts.
  • Status- After items were reviewed we’d put ‘Reviewed’ in this column. We’d also use color highlighting in this column to indicate items that might need a second review or that were particularly troublesome to clean up.


We used the following general approach while reviewing each converted file:

  • Check the ‘Conversion Issues’ output from the conversion tool for any issues reported with that file (this output was dumped into another shared Google docs spreadsheet after the initial conversion was complete)
  • Fix any compiler errors (ReSharper was very helpful as it was easy to see how many compiler errors were present in any given file).
  • Force a regeneration of the .designer.cs files by making a minor no-op tweak (e.g. add a whitespace and then remove it) to the .aspx markup and saving it again.
  • Use ReSharper to clean up any ‘using’ statements that were importing unneeded namespaces (the conversion pulled a ton of unneeded using statements into each code-behind file).
  • If any bit of code seems odd, compare it with the original VB .NET

Our DevCon meetings take a Monday-Friday schedule and we began the conversion review and cleanup on Monday morning. By Wednesday night we had the project compiling successfully and were able to update our build scripts to start pushing the newly converted code out to our QA machines for regression testing.

Regression Testing

The regression testing effort turned into an all-company affair as we ended up enlisting members of the support and product management team in addition to developers and QA analysts to get it all done. Even with the added resources, however, it wasn’t feasible to fully regression test all 600 pages in the application. We did some quick brainstorming and came up with a prioritized list of the pages and services exposed by the web application that would need the most rigorous regression testing. We used the two pretty simple criteria to determine what areas of the application needed the most attention:

  • Does the page/service alter data or just display it? (i.e. is there a potential for data corruption?)
  • How often does the page get used (Google Analytics, IIS logs, and some home-grown analytics tools helped us answer these questions?)

Areas of the application that both alter data and/or are used a lot got the most attention from our QA team while those that only read data but are also used frequently were the second highest priority. For areas of the site that were not used very frequently and had low risk for data corruption were only “smoke-tested” to ensure that they executed without throwing any errors. We added a couple of columns to the shared Google Docs spreadsheet that we used to review each file to keep track of files that had been tested and whether or not any issues had been found. One nice side effect of this exercise was the identification of a few pages in our website that were not being used anymore and could be deleted.


One of the biggest challenges we had during the test effort was figuring out how to test certain areas of the code that are not easily accessible. For example, code-behind files for shared user controls (.ascx) had to be converted and reviewed, but our QA team didn’t necessarily know what pages those controls were used on or how to exercise their code. In some cases a user control might only appear when a certain configuration is enabled and that required that a developer work with them to figure out whether or not each bit of shared code had been exercised adequately.


The regression testing effort ended up spilling over into the week following the DevCon meeting, but we were ready to deploy the converted code by the middle of the week following.

Rollout

We knew that we hadn’t been able to fully regression test every bit of the converted code and were certain that issues would crop up once the code was out and being used in our production environment. We maintain a production environment that many of our customers use in a SaaS model and several of our customers self-host our application on their own hardware. Our production environment has a load-balanced web farm which we decided to use to our advantage for the initial rollout of the converted code.


Once our regression testing was finished we created a release branch of the converted code just like we would for any other normal deployment. We then took some of the servers in the farm out of the load balancer rotation and modified our deployment scripts to push the converted code from the release branch to those now offline web servers. Once the deployment was done, we swapped them back into the load-balancer rotation and removed the ones with the older VB .NET code.


When issues were found and reported, we were able to quickly swap out the servers and get the older reliable VB .NET code running in production again while we patched the code in the release branch, re-deployed to the offline servers and swapped them back in. I don’t recall the exact numbers, but I don’t’ think we had to do this more than 5-6 times. Because we had created a release branch we were able to let most of the development team get back to new development work against trunk while a portion of the team focused on fixing any issues that cropped up with the conversion in the release branch. Any fixes that were made in the release branch were merged back to trunk. By the end of the week following our DevCon the conversion project was officially finished and the entire team returned to our normal flow of development work. I think we still fielded a few minor bugs related to the conversion here and there over the next couple of weeks, but most of the issues shook out within the first week.

Lessons Learned

I have a few key takeaways from this effort:

  • We had no idea how long this would take us before we started and we didn’t bother trying to do much in the way of estimating. Instead, we just committed ourselves to getting it finished and powered through for as long as it took. We had to make some adjustments along the way (e.g. pulling in the support team to help with testing and only keeping 2-3 developers to fix the conversion issues after the initial rollout), but I think it all came together as well as it could schedule-wise.
  • I wish I had done more to track how many hours we spent on the conversion project. I could estimate based on my (likely faulty) recollection of the project, but it would be nice now to have a feel for how much the conversion really cost us in terms of hours.
  • The process of identifying every endpoint in our web application and, at a minimum, smoke-testing each one is an excellent housekeeping exercise. We identified a few areas of our application that are not used anymore and identified a few long-standing bugs that were not related to the conversion effort at all. I’d like to have us go through this process again, maybe once every 2 years or so. Also, by enlisting the majority of the company in this effort were ended up exposing some folks to areas of the app that they normally never see and ended up sharing a lot of valuable application-specific knowledge with each other.
  • I don’t think we could have done this as quickly as we did if we didn’t have the entire team in the same room for a week. I might even go a step further and say that we might not have even been able to pull it off at all.
  • I think that the collective confidence of the team in a successful outcome for this project ebbed and flowed quite a bit throughout the project. There were a few occasions when we encountered a very odd issue that gave us pause and made us wonder if we were in for a disaster. Having a boss who really understood the value of the project and encouraged us to press on and complete it despite the bumps in the road was crucial to the success of the project.
  • Because I did the initial conversion and committed it to source control many of the files in our website project now only have a single commit in their history (with my name on it). We moved the original VB .NET project to an “archive” location in our source control tree so we can still go digging and find earlier revision history for a file if we need to, but until then all questions of, “who last worked on this file” usually end up being answered with my name. If I were to do this over again I might investigate a method to migrate the commit history of each of the original .vb files to their .cs counterparts, but at this point it’s not worth the effort.
Within a few months of finishing the conversion project, it was hard to imagine that we used to have so much VB .NET in our application. By doing this conversion we were able to remove the, “VB .NET (but we’re phasing it out)” bullet point from our developer job postings and reduced the various VB .NET related grumblings in our team chat logs by 100%. It was a big job, but I think it was worth it in the end.
Posted On Thursday, December 13, 2012 10:08 AM | Comments (0)
Meta
Tag Cloud