Tuesday 10 May 2011

Migrating from TFS 2008 to SVN 1.6 with tfs2svn

This is tricky because tfs2svn stopped being updated when subversion 1.4 ruled the roost. 

Here are the steps that I took:

  1. Have a subversion repository ready to receive the project (including ‘pre-revprop-change’ hook – mine is a simple ‘exit 0’ batch file)
  2. Install TortoiseSVN 1.6
  3. Download Collabnets command line client for svn 1.6 (svn-win32-1.6.15.zip)
  4. Install tfs2svn 1.1 (http://sourceforge.net/projects/tfs2svn/files/tfs2svn/1.1/tfs2svn.setup.msi/download) on my development VM
  5. Extract the svn-win32 command line client and copy the contents of ‘bin’ folder over the contents in ‘C:\Program Files (x86)\Kevin Colyar\tfs2svn\libs\svn-win32-1.4.6\bin’
  6. Checkout revision 104 of tfs2svn using TortoiseSVN (repository url: https://tfs2svn.svn.sourceforge.net/svnroot/tfs2svn)
  7. Save the patch from http://sourceforge.net/projects/tfs2svn/forums/forum/772710/topic/4510118 as tfs2svn_update.patch (make sure all &amp; are converted to & and all links have any <a> tags removed)
  8. Using TortoiseSVN apply the patch to the trunk branch.  All the files should be matched successfully.  If not check for stray html encoding/links in patch file.
  9. Open the tfs2svn.sln from trunk (upgrading the solution if using VS2008)
  10. Build ‘tfs2svn.WinForms’ and copy the output over the contents of ‘C:\Program Files (x86)\Kevin Colyar\tfs2svn’ apart from the ‘libs’ folder.
  11. Launch tfs2svn and fill in the fairly straightforward form and hit convert!

tfs2svn should now spin through your TFS repository and migrate each changeset to subversion (keeping the history).  The few errors that I encountered (after applying the patch!) were easy to fix as they were missing folder (from svn) issues when a changeset included a branch or merge operation.  Simply creating and committing the missing folder  allowed the migration to continue.

Tuesday 3 May 2011

Logging is the new Exception Swallowing

For a long time now I’ve been stamping down hard on empty catch blocks in code, for obvious reasons.  When I can dictate coding standards that’s pretty much top the list. 

  1. Every ‘catch’ block must (at a minimum) log or throw

I now realise I made a mistake with this rule...it should be:

  1. Every ‘catch’ block must (at a minimum) throw or log

It’s a subtle (the wording – not the formatting) difference in the way we need to think about this rule but it’s one I think we don’t think about enough. 

For example, this is a common pattern of code that I’m seeing during Code Review sessions:

   1: public bool PerformCriticalTask()
   2: {
   3:     try
   4:     {
   5:         CriticalFunctionalityTaskA();
   6:         CriticalFunctionalityTaskB();
   7:         CriticalFunctionalityTaskC();
   8:         return true;
   9:     }
  10:     catch(Exception ex)
  11:     {
  12:         Logger.Log(ex);
  13:     }
  14:     return false;
  15: }
  16:  
  17: public CriticalFunctionalityTaskA()
  18: {
  19:     try
  20:     {
  21:         //Do Important Stuff Here
  22:     }
  23:     catch(Exception ex)
  24:     {
  25:         Logger.Log(ex);
  26:     }
  27: }
  28:  
  29: public CriticalFunctionalityTaskB()
  30: {
  31:     try
  32:     {
  33:         //Do More Important Stuff Here
  34:     }
  35:     catch(Exception ex)
  36:     {
  37:         Logger.Log(ex);
  38:     }
  39: }
  40:  
  41: public CriticalFunctionalityTaskC()
  42: {
  43:     try
  44:     {
  45:         //Do The Most Important Stuff
  46:     }
  47:     catch(Exception ex)
  48:     {
  49:         Logger.Log(ex);
  50:     }
  51: }

It’s pretty clear that this Catch/Log pattern has become a developers default exception handling boiler plate code.  It’s adding no value and actually making the troubleshooting processes more complicated and time consuming. 

My response to this is simple…remove all exception handling!

In the vast majority of cases, if an exception is thrown the application is already broken and letting it limp along is an act of cruelty.  The application should handle these critical failures as exceptional and handle all of these events the same way in an application scoped way (Custom Error Page for Web Applications, etc).  By all means log the exception at the application scope level, it will have actual diagnostic value there(complete stack trace, etc).

Of course, there are exceptions to this policy such as when you can legitimately recover from the fault.  But these cases are few and far between.

Monday 2 May 2011

Base26 Number Encoding/Generate Excel Columns

Ever needed to convert an Excel Column Letter to a number or vice versa?  Here’s a couple of extension methods to do it:

   1: public static string ToLetterEncoded(this Int32 numberToEncode)
   2: {
   3:     if (numberToEncode <= 0) throw new ArgumentOutOfRangeException("numberToEncode", numberToEncode, "Value must be greater than zero");
   4:     numberToEncode--;
   5:     if (numberToEncode >= 0 && numberToEncode < 26)
   6:         return ((char)('A' + numberToEncode)).ToString();
   7:     else
   8:         return ToLetterEncoded(numberToEncode / 26) + ToLetterEncoded(numberToEncode % 26 + 1);
   9: }
   1: public static Int32 FromLetterEncodedInt32(this string letterEncodedNumber)
   2: {
   3:     if (letterEncodedNumber.IsNullOrWhiteSpace()) throw new ArgumentNullException("letterEncodedNumber");
   4:     char[] letters = letterEncodedNumber.ToUpperInvariant().ToCharArray().Reverse().ToArray(); //smallest column first
   5:     if (letters.Where(c => !char.IsLetter(c)).Any()) throw new ArgumentOutOfRangeException("letterEncodedNumber", "Encoded Number must only contain the letters A-Z");
   6:     int[] numbers = letters.Select(c => (((int)c - 'A') + 1)).ToArray();
   7:     Int32 columnNumber = 0;
   8:     for (int i = 0; i < letters.Length; i++)
   9:     {
  10:         columnNumber += (Int32)Math.Pow(26, i) * numbers[i];
  11:     }
  12:     return columnNumber;
  13: }

I’ve used Int32 to represent the type of integer that you’re en(de)coding, for easy find ‘n’ replace.

These will only work for positive integer (1 and over).