Monday, December 07, 2009

Hallelujah ! New TFS 2010 Check-in Policy

I learned a few new things about TFS today, and the most exciting piece of news is about a new check-in policy in the TFS 2010 Power Toys. This new policy lets you apply different sets of (other) check-in policies to different source control folders. That means you can set code analysis at different levels for different projects, or even better, turn it off completely for folders that just contain sql scripts, images and other files that don’t require code analysis !

A HUGE thank you to the TFS Power Toys team for inventing this ! I actually made a suggestion via Microsoft Connect for this feature, but they marked it as closed by design so I wasn’t holding out hope for a fix. I’m glad someone saw sense and invented a fix, even if it is a power toy.

 

The two other things I learned, and these probably apply to TFS 2008 and maybe 2005 as well;

1. You can set the default lock type in TFS based on file type (extension).

2. If you have a check-in lock on a file, anyone else checking out that file gets notified of the lock at check out time.

 

That solves (well, nearly) an issue my boss has with allowing multiple check-out. To be fair, it would be nicer if we could get a warning even on a check-out lock (since multiple checkout doesn’t happen often in our place, we just want to know when it does), but perhaps we can live with it the way it works.


Technorati Tags: ,,

Sunday, December 06, 2009

Unique Indexes versus Unique Keys (Sql Server 2005)

I originally posted this article a few weeks ago, but pulled it after a day or two as I discovered I had made an error in my testing which invalidated some of my conclusions. I have now updated the article and reposted it, if you read the original please disregard it and read this version instead.

During a recent meeting with my colleagues, the question was raised, “What is the difference between a unique index and a unique key in Sql Server ?”. It turns out nobody really knew, and some people always used one, others used the other, and others had semi-arbitrary reasons for choosing one over the other.

After the meeting I jumped on the internet and tried to find out the answer, unfortunately it wasn’t that easy. Most of the answers said that a unique index and a unique constraint are the same but you use a constraint to indicate the data is unique in itself (i.e. social security numbers, which it turns out aren’t actually unique anyway).

However I wasn’t convinced a constraint was the same as a key (it seems that in terms of unique ones they are very similar), or that if they were the same that there were no functional differences between a unique index and a unique key/constraint. So to that end, I decided to do some testing myself. I have only done very basic checks, and only comparing indexes and keys, not specifically constraints. Here’s what I’ve found (at least as far as Sql 2005 goes);

Unique Keys Are More Unique

Unique indexes allow multiple rows with null values, unique keys require each row to be unique including which columns contain nulls. The most simple case is an index and constraint with a single column in each. The index will allow you to have multiple rows where that column is null, the key will only allow a single row where that column is null.

In the case of multiple columns I presume the index is fairly easy going, so long as all the values in all the columns that aren’t null are unique then it will allow the row to be added as well as multiple rows where all the columns are unique. In the case of a key, the same column might be allowed to be null in multiple rows so long as the combination of other columns in the key can uniquely identify that row.

Unique Keys Can Be Used In Foreign Keys

A column that has a unique key on it can be used in a foreign key constraint, however a column with a unique index cannot be (unless perhaps it has a unique constraint as well). Presumably this is related to the first issue, in that a unique key actually guarantees a single row can be found where as a unique index might return multiple rows where null values are involved.

Sql Management Studio GUI Differences

First of all, keys, indexes and constraints are all in separate places in the Object Explorer window of Sql Management Studio. Once you expand the parent table, there are sub-nodes for each (keys, indexes, constraints) and each item is listed under the appropriate category. This is a minor difference but does mean you might have to look in multiple places to find the item you’re looking for if you don’t know what type it is.

Secondly, keys seem to be the poorer cousin to indexes in terms of access to features and related information through Sql Management Studio. When you right click on an index you get options to Rebuild or Reorganise the index, both of which effectively ‘defrag’ the index data in the database and can optimise performance. In the ‘properties’ of the index you can also seem information about how fragmented it is.

Keys do not have either of these options, nor any properties that can be accessed via the GUI. It seems you can run the T-Sql commands to rebuild or reorganise the keys, likewise setting options like storage locations, sorting in tempdb, and index locking options can all be applied when the key is created via T-Sql but do not seem to be available when creating a key via the GUI.

Some options are only available on indexes and not on keys, whether using T-Sql or the GUI. These features include ‘disabling’ an index, and ‘included columns’ which can be used to improve the performance of read based queries at the expense of space used and insert/update performance.

Deleting keys and indexes is also different. Using T-Sql you seem to be able to delete either fine. Using the GUI deleting a unique index is no problem, but deleting a unique key often fails with an error saying the key is being used to enforce uniqueness in the table and the delete is cancelled. I’m not sure exactly what causes this but I think it’s as simple has having rows in the table at the time you try to delete the key.

Similarities

Both unique indexes and unique keys are used by the optimiser the same way, that is, it is not necessary to create a unique index and a unique key for performance reasons. Also, both keys and indexes automatically create a set of matching ‘statistics’ used by the optimiser to create the best query plan possible.

Columns with unique indexes or keys can also both be used in foreign keys, there is no difference between them as far as foreign keys are concerned.

Conclusion

Unique indexes and unique keys are extremely similar and for common purposes there is very little difference, but they are not exactly the same in detail. Differences in management ability via the Sql Management Studio GUI as well as advanced performance options such as included columns can be important in edge cases.

If anyone knows of any more differences, or any additional detail, please post a comment to let me and others know.


Sunday, November 29, 2009

TryParse for the Compact Framework

One of the annoying things about the Compact Framework is that it doesn’t contain the TryParse methods that are found in the full framework.

At the company I work for we have some applications and common libraries that have both compact and full framework versions. Rather than repeat the code in each, we share the class files between the two projects (full/compact versions) and use conditional compilation where necessary to alter the code to match one framework or the other… unfortunately there isn’t a good and simple solution for the missing TryParse methods.

In theory it is possible to write your own functionally correct TryParse methods, but I haven’t yet done this (I intend to eventually). What I have done, in the meantime, to allow our code to compile is created a static ParseAssistant class. This class has static method overloads for all the usual TryParse methods we use, and our developers have been told to use these methods instead of the normal .NET ones. This means their code will compile on either framework… the only downside is that because I haven’t yet written ‘proper’ try parse code I have just conditionally compiled in (for the compact framework) a Parse with exception handling to report if the parse failed. This isn’t performant, but only applies in the compact framework and is easily rectified at a later time when it becomes a problem. In the meantime, should anyone else have the same problem here is the code we’re currently using;

/// <summary>
/// Contains methods to assist with parsing one value into another.
/// </summary>
public static class ParseAssistant
{
  #region TryParse Overloads
  /// <summary>
  /// Attempts to parse the string provided into an integer value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out int result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = Convert.ToInt32(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = int.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into a byte value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out byte result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = Convert.ToByte(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = byte.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into an Int16 value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out Int16 result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = Convert.ToInt16(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = Int16.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into an Int64 value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out Int64 result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = Convert.ToInt64(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = Int64.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into a decimal value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out decimal result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = Convert.ToDecimal(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = decimal.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into a float value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out float result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = (float)Convert.ToDecimal(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = float.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into a double value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out double result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = Convert.ToDouble(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = double.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into an sbyte value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out sbyte result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = (sbyte)Convert.ToInt32(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = sbyte.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into a uint value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out uint result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = (uint)Convert.ToUInt64(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = uint.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into a ulong value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out ulong result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = (ulong)Convert.ToUInt64(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = ulong.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into a ushort value. 
  /// </summary>
  /// <remarks>Returns 0 in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or zero if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out ushort result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = (ushort)Convert.ToUInt64(s);
            retVal = true;
        }
        catch (FormatException) { result = 0; }
        catch (InvalidCastException) { result = 0; }
#else
    retVal = ushort.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into an <see cref="System.DateTime"/> value. 
  /// </summary>
  /// <remarks>Returns <see cref="System.DateTime.MinValue"/> in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or <see cref="System.DateTime.MinValue"/> if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out DateTime result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = Convert.ToDateTime(s);
            retVal = true;
        }
        catch (FormatException) { result = DateTime.MinValue; }
        catch (InvalidCastException) { result = DateTime.MinValue; }
#else
    retVal = DateTime.TryParse(s, out result);
#endif
    return retVal;
  }
  /// <summary>
  /// Attempts to parse the string provided into an integer value. 
  /// </summary>
  /// <remarks>Returns false in the result parameter if the parse fails.</remarks>
  /// <param name="s">The string to attempt to parse.</param>
  /// <param name="result">The result of the parsed string, or false if parsing failed.</param>
  /// <returns>A boolean value indicating whether or not the parse succeeded.</returns>
  [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "s")]
  public static bool TryParse(string s, out bool result)
  {
    bool retVal = false;
#if WindowsCE
        try
        {
            result = Convert.ToBoolean(s);
            retVal = true;
        }
        catch (FormatException) { result = false; }
        catch (InvalidCastException) { result = false; }
#else
    retVal = bool.TryParse(s, out result);
#endif
    return retVal;
  }
  #endregion
}

You’ll need to add a ‘Conditional Compilation Symbol’ called WindowsCE to the project build properties of your compact framework projects for the conditional compilation to work correctly.


Obviously you could turn these into extension methods where supported, but as I work mostly in the .NET 2.0 framework I haven’t done this.






Sunday, November 22, 2009

The WTF Operator

Do you know what the ?? operator does ? I like to call it the WTF operator, as a lot of people have never seen it and can’t pick what it does immediately and because it’s not uncommon to see ?? after WTF (WTF??). It’s actually called the null-coalescing operator.

Basically it returns the value to the left of itself if that value is NOT null, else it returns the value to the right. For example, the following code sets ‘customer’ to either the m_Customer field or a new Customer object depending on whether or not m_Customer is null;

CustomerBase customer = m_Customer ?? new Customer();



Of course you can nest the ?? operator to build a form of coalesce statement;



private CustomerBase GetCustomer(CustomerBase customer)
{
return m_Customer ?? customer ?? new Customer();
}


Whether you should or not is another question, as readability and maintainability are more important than conciseness.



Hmmm, conciseness… is that really a word ? Spell check seems to think so.






Technorati Tags: ,,,,,

Wednesday, November 18, 2009

You Don't Know Jack About Software Maintenance | November 2009 | Communications of the ACM

Contender for "Quote of the Day" (from article below:

"In software, adding a six-lane automobile expressway to a railroad bridge is considered maintenance—and it would be particularly valuable if you could do it without stopping the train traffic."

You Don't Know Jack About Software Maintenance November 2009 Communications of the ACM

Saturday, November 14, 2009

Sandcastle Help File Builder (SHFB)

If you’re looking to create developer help from the xml comments in your code, I recommend Sandcastle Help File Builder (SHFB) along with Microsoft’s Sandcastle itself.

Version 1.7.0.0 is a great stable release and excellent for manual generation of code (I’ve never tried scheduling it, had problems using it from MSBuild).

Version 1.8.0.2 looks like a complete re-write so it uses MSBuild and MSBuild file formats, which makes it excellent for integrating into Team Foundation Server, but it’s still in the beta stage.

If you’re looking to download Sandcastle itself, try the Visual Studio 2008 SDK or the Visual Studio 2005 SDK one.


Sunday, November 08, 2009

Conspiracy Theories: Planned Obsolescence and Deliberate Bugs


Something that really annoys me is when otherwise intelligent people start buying into conspiracy theories around software development and software companies. If I had a dollar for every time someone had suggested that software companies and developers either deliberately plan for their products to be obsolete just so they can charge for upgrade, or deliberately implant bugs in their code to force users to pay for upgrades then I’d be a very rich man.

If you believe this, trust me, you are dead wrong. A few quick Google’s for phrases '”How to write better code”, “Improving Code Quality”, and “Unit Testing” should show you how hard the industry works to prevent bugs in the first place and remove bugs that do occur. Millions, if not billions of dollars, and hours and hours are spent on this. We build tools, create methodologies, work slower, think more, change designs, change architectures, change technologies all in an effort to get eliminate bugs one way or another. The fact is that writing great code is hard, if not impossible. Eric Lippert has on his blog (Fabulous Adventures In Coding) suggested that it is not rocket science… that it is harder than that. And he’s not the first to have done so.

As for planned obsolescence, I guess there is an element of truth. When a software company or developer begins a new project or product, they likely realise on some level that there will be a version 2, or 3, or 4 so long as the company doesn’t go broke before then. Where this theory is wrong is that is suggests we do this to make money. Typically, we do this to cut costs. We often know we can’t survive as a business if we try to create the first version with every feature in it we want. If we think we can or try anyway, the development process often turns out to be harder than we expect and features are dropped so we can start selling the product to pay for the rest of the development.

Even if we managed to produce a product that was a hundred percent feature complete (in terms of our original requirements), and had zero bugs, that wouldn’t guarantee there wouldn’t be another versions. Users request new features, or changes. Businesses, and their processes and requirements evolve and require the software to evolve with them. That’s without considering that most software is built on top of other software, and certainly hardware, and these things change in response to the requests made by other users of those systems… some of these changes will necessitate changes in the software built on top of them. No where in this process is there any thought that we should deliberately hold back on features or fixes just to sell the next version.

The really annoying thing is other industries have the same issue with planned obsolescence, but very few people if anybody, complain about them. When GM or Ford release a new model of car, does anybody, including the staff at the company producing it, think this is the last model they will ever produce ? Does anyone sit around saying, damn, last year I bought the Falcon XR8 and now they’ve released the Hawk QT80.. they must have done that just to make more money !

Of course, new models of car (and versions of software) are designed to generate revenue, they must for the company to survive. You want the company to survive because you want after sales support, improved products when you are ready to replace your existing items and so on… but really Ford and GM, and anyone else in business, releases new products because technology changes, knowledge increases and we find a way to build something better than we did last time.

This process isn’t designed to be hurt our customers, to charge them more. It designed to help them, and even if we wanted to avoid it we can’t. More to the point, the software industry isn’t the only one in which this happens but plenty of people call it out as if it was.

Why people are so ready to believe these conspiracy theories is beyond me. I can only presume they’ve had a bad experience, perhaps had to pay for an expensive upgrade or had one that didn’t go well. This has left them angry and looking for someone to blame so they start attacking the industry.

Unfortunately, they’re not only wrong but they come off looking stupid to anyone who actually understands the reality of the situation and how hard we work to avoid these issues.