May 2007 - Posts
Off on a bit of a tangent this morning...
I have a bit of a love/hate relationship with the Visual Studio start page. I do find it useful enough when I first open VStudio. However, once I start working on anything, the mere presence of the start page tab in the IDE main window is really, really irritating to me for some reason. Of course, given that even one little using directive out of place also drives me to distraction, this probably isn't too surprising.
At any rate, the other day, I decided that it was high time to do a little something about that start page annoyance. At some point in the medium-ish future, I might turn Bordecal.ImportSorter into Bordecal.ToolsForObsessiveCompulsiveNitPickers and throw in a start page closer. In the meantime, here's a VStudio 2005 macro to close the start page as soon as any document is loaded in the IDE.
For those of you who are interested in using the macro, here are a couple of picky details:
- The start page will be closed when any document is opened, regardless of how it was opened. If you open a solution that has open documents, that's good enough to trigger closing of the start page.
- The start page will only be closed once. If you reload the start page manually, the macro won't close it on you.
The above are completely intentional features--that's exactly how I personally prefer the start page to be closed. If you would prefer some alternate behaviour, please feel free to ask. I'm quite willing to consider adding customization of the behaviour when I eventually get around to adding this stuff to an add-in...
I skipped ahead a while back with my post on the exceptions theme, and it's time to get back on track with stuff that would usually precede that rather involved topic during an FxCop backlog cleanup project.
The good news with the disposition and finalization topic is that its scope is quite a bit narrower than the exceptions topic. The bad news is that you'll probably see almost as many preconceptions regarding how it works, particularly if you have quite a few developers who are accustomed to deterministic finalization. As with the exceptions topic, it's probably a pretty good idea to start the topic from scratch, making the assumption that most of the developers on your team will get at least something from coverage of even the fundamentals of the subject matter.
Resources
This time around, Chris Brumme's post on finalization is absolutely required reading, at least for the topic trainer. If you don't understand at least 90% of that article, you probably don't know enough about finalization to be teaching the topic.
You'll also need some resources that are more on the "how to" side of things. The MSDN articles Implementing Finalize and Dispose to Clean Up Unmanaged Resources and Implementing a Dispose Method to provide a reasonable starting point around guidelines for finalization and disposition. By the time you complete your training on finalization and disposition, all the developers on your team should be able to read and understand these articles.
Some gotchas
By and large, the documentation available is pretty clear, and the recommended practices are relatively unambiguous. However, we did run into a few picky detail issues while tackling the disposition and finalization theme at FinRad:
- In a disposable class that contains a collection of disposable items, how does one deal with exceptions that might be thrown during the disposition of any individual item?
The issue here is that one wants to continue disposing of any remaining items even if an exception is thrown while disposing of any given item. However, one still wants to throw an exception out of the parent class Dispose method. The question here is exactly what should one end up throwing? One answer is to store any exceptions into some custom exception that contains an exception collection, then throw the resulting beastie at the end of the process. Unfortunately, there are several problems with this approach, not least of which is that the original Dispose method caller isn't likely to be want to iterate over the exception collection trying to decide what to do about each and every one of the member exceptions.
What we ended up deciding to do instead was to treat disposition of each individual collection item as if it we were disposing a field on the parent class. In other words, we wanted to expose the same exception scenario that would be seen if we were able to code the parent Dispose method like this: try
{
this._someDisposableField.Dispose();
}
finally
{
try
{
this._someOtherDisposableField.Dispose();
}
finally
{
try
{
this._collectionOfDisposables[0].Dispose();
}
finally
{
try
{
this._collectionOfDisposables[1].Dispose();
}
finally
{
...
}
}
}
}If one were able to do this, the only exception the caller of the parent class Dispose method would see is the last exception thrown in any of the individual try blocks. This is quite easy to simulate even while disposing in a loop. e.g.: try
{
this._someDisposableField.Dispose();
}
finally
{
try
{
this._someOtherDisposableField.Dispose();
}
finally
{
try
{
Exception lastException = null;
foreach (IDisposable item in this._collectionOfDisposables)
{
try
{
item.Dispose();
}
catch (Exception ex)
{
lastException = ex;
}
}
if (lastException != null) throw lastException;
}
finally
{
GC.SuppressFinalize(this);
}
}
}Is this ideal? Obviously not, but I happen to believe that it presents less problems than any of the alternatives. If you think you have a better approach, I'd love to hear about it.
- What exactly is an unmanaged resource?
The basic rules for implementing disposition and finalization are the following:
- If a class has a disposable field, it should disposed from the
Dispose method of the parent class.
- If a class has a field that represents an unmanaged resource, this should be released from both the
Dispose method and the finalizer of the parent class.
The first rule is pretty unambiguous, but what exactly qualifies as an "unmanaged resource" that should be released both on disposition and finalization of the parent class? Most of the articles on disposition and finalization make some airy-fairy mention of things like database connections and file handles, but does that mean a SqlConnection should be disposed in the parent class finalizer? The simple answer is "no". The barely more complicated answer is pretty much the only things you should be cleaning up during finalization are IntPtr fields. If you've gone beyond IntPtr to start using SafeHandle in .NET 2.0, you don't even need worry about those since the unmanaged handle is already wrapped in a disposable, finalizable managed object.
- There's no framework design guideline with respect to interface inheritance from IDisposable, but there probably ought to be.
Interfaces that are likely to be implemented by types that will need to be disposable should themselves be disposable. This is because you don't want to end up having to write code that looks like this: IShouldBeDisposableButIsNot thingy = Factory.CreateThingammy();
try
{
//...
}
finally
{
IDisposable disposableThingy = thingy as IDisposable;
if (disposableThingy != null) disposableThingy.Dispose();
}Instead, you want to be able to simply write code like this: using (IShouldBeDisposableAndIs thingy = Factory.CreateThingammy())
{
//...
}
The built-in rules
Here are the built-in rules that you'll want to start activating during the disposition and finalization theme:
Some custom rule ideas
We haven't created any custom rules yet around finalization and disposition at FinRad, but there are at least a couple that I would like to implement eventually:
- IDisposable.Dispose should be called from within a finally block (and the only other code in the finally block should be a cast to IDisposable)
- An interface with at least one disposable implementation should itself be disposable
That last one is bound to be interesting to try to author, but I've got loads of time to figure out how to implement it since there are quite a few more important rules to tackle first...
With profound apologies to VB lovers, there are a few features of the VB compiler that occasionally make me want to start drinking at a very early hour of the day. Perhaps the most troublesome of these is its failure to start screaming bloody murder upon detection of namespaces that differ only by case, even while compiling an assembly that is marked as CLS-compliant. VB 2005 is at least kind enough to conserve the casing from the source code. However, VB 2003 seems to more or less randomly pick one case version and apply it throughout an assembly, and that leads to all sorts of fun and games.
If you've developed mixed VB and C# solutions, chances are pretty good that this isn't exactly news to you since C# tends to get mighty unhappy the resulting changes in namespace casing. However, you might be blissfully unaware that FxCop is also case-sensitive, and that has its own special set of consequences...
At FinRad, we're still supporting applications that run against .NET 1.1 (and will be for at least a couple more years), and a very large part of that code base is written in VB 2003. Given that this is .NET 1.1 code, we're using the stand-alone version of FxCop for static analysis, and exclusions are stored in the FxCop project file, not as SuppressMessageAttribute annotations in the source code.
Given that this also happens to be the same code base covered by the bulk of our backlog cleanup efforts, the FxCop project files contain an unusually large number of exclusions. (I'll write about the reasons for this soon. For now, just trust me: lots more violation exclusions than you might normally expect.) Every now and then, we would end up with a situation where a sizeable chunk of our exclusions would suspiciously disappear, and all the violations they covered would auto-magically become active again. Needless to say, this was terribly annoying, but we chalked it up to gremlins, manually re-created the exclusions, and forged bravely ahead.
A few weeks ago, we hit a really weird problem where an automated build running on an integration server was detecting active FxCop rule violations that couldn't be duplicated on any developer machines. Several of us tried clean check outs, rebuilding, etc., but without any success at reproducing the problems seen in the automated build. In retrospect, it seems painfully obvious, but the namespace casing problem (which also tended to raise its ugly head in a Visual Studio vs. automated build disjunction with respect to C# callers) didn't occur to me until I was stuck in traffic that evening. It was the first thing I checked the next morning and, sure enough, the exclusions were registered under the "Something" namespace, but the classes compiled in the automated build now fell under the "SomeThing" namespace.
Some lessons learned:
- FxCop is case-sensitive.
- The gremlins actually lived in the VB compiler. (Luckily, they've been evicted, but the nasty smells are lingering.)
- Giving an "aha!" smack to one's forehead while stuck in traffic will earn some very concerned looks from drivers of neighbouring vehicles.
Since I started monitoring traffic on this blog a little more closely about a week ago, I had the unexpected surprise that the posts on HTML encoding and server vs. client cultures were getting a lot more hits than I expected. I had been planning on starting a series of "how to" posts on those topics this weekend, but that was before David Kean from the FxCop team was kind enough to direct a bunch of folks my way with a post about my recent FxCop posts. Since it would seem that I've now got quite a few new subscribers who are interested in FxCop, I figured I might as well continue on that theme for a while...
Jumping the gun
I've got exception stuff on the brain this morning, so that's what I'm going to write about. However, if you have decided to tackle an FxCop backlog, exceptions are probably not the first major theme you should cover. Then again, even assuming that my recent posts have inspired you to immediately start a backlog winnowing project, you should still be at least a month away from covering any major theme. I'm going to try to write about the much easier disposition and finalization topic within the next couple of weeks. Heck, it might even happen tomorrow morning if I don't get coerced into some sort of housework. In the meantime, just keep in mind that the sequence in which I end up covering the topics here isn't necessarily the sequence in which you'll want to cover them in your backlog process.
Starting from scratch
Unless your team already has very specific guidelines in place around the generation and handling of exceptions (along with training and code reviews to reinforce those guidelines), it's pretty safe to assume that there is a very wide variety of exception treatment in your code base. In addition, the developers on your team likely have similarly disparate understandings of when and how exceptions ought to be generated and handled. Instead of jumping right into covering the existing FxCop rules around exceptions, it would probably be a much better idea to start with some training around the basics of exceptions. Chances are excellent that individual developers will be carrying quite a bit of baggage based on their experiences with exception treatment in other languages or platforms, and you will most likely need to devote quite a bit of time to dispelling inappropriate preconceptions.
Here are a few resources that you may wish to consult during your exception training:
- The .NET Framework Design Guidelines for Exceptions
Some parts are a bit outdated, and the formatting doesn't necessarily make for easy reading, but this is probably still the best place to start.
- The .NET Framework Developer's Guide to Handling and Throwing Exceptions
Some parts are very basic, but the Best Practices for Handling Exceptions section is good complement to the design guidelines.
- Krzysztof Cwalina's posts on exceptions
Most of the information contained in these posts ought to be in the official design guidelines documentation on MSDN. Unfortunately, since the latter content doesn't seem to get updated all the often, you'll need to read Krzysztof's posts as well.
- The FxCop team FAQs on which exception types to throw and why you should catch System.Exception
By a stroke of luck, the "what to throw" FAQ came out just as we were wrapping up the exceptions topic at FinRad, which saved me the trouble of writing a very similar set of guidelines for internal consumption.
- Rico Mariani's posts on the costs of exceptions
This content is helpful if you're trying to understand the cost of exception handling, but watch out for a bit of performance-oriented bias. If you're concerned about the usability of your APIs, you'll probably end up wanting to throw exceptions a little more often than Rico might like. ;)
- Chris Brumme's post on the exception model
Interesting as it is, this isn't likely to be required reading for most folks on your team. However, it is something you might want to read if you're going to be the one giving the training on the exception topic.
If you're concerned about globalization and localization issues, you might also want to have a look at my post on client vs. server cultures. The disparity between application user and administrator languages can present challenges when emitting localized exception messages for any application, and it's something you may wish to consider as you develop your internal exception generation guidelines, even if you don't happen to be writing web applications.
The built-in rules
As part of the exceptions theme, you'll want to start activating the FxCop rules that touch on exception generation and handling. The following table lists the rules available in FxCop 1.35 and Visual Studio 2005 static analysis:
I suppose that it could be argued that Usage.DoNotIgnoreMethodResults might also be worth including while activating exception rules. If your team is resistant to the idea of throwing exceptions instead of using failure return codes, then a bit of time cleaning up violations of this rule might be just the thing to convince them. Otherwise, it's probably safe to activate this rule whenever it otherwise seems reasonable during your backlog process. If you're following the guidelines, you'll probably want to fix most violations of this rule by modifying the invoked method to throw exceptions rather than return a status code, which is a breaking change for public methods, and scheduling breaking changes can be a wee bit tricky.
Custom rules
If you've read the design guidelines concerning exceptions, it should be pretty obvious that the above eight rules don't exactly cover all the potential problems for which you might want to screen. Here are a few custom rules for exceptions that we're either already using at FinRad or will be introducing once we get a little further along in the backlog cleanup:
- Do not throw System.NotImplementedException
Pretty obvious, although you'll occasionally need to create temporary exclusions, particularly for when someone checks in a class skeleton to be fleshed out by somebody else. Also, as a bit of side note, when you introduce this guideline, you might want to stress that the exception signifies that the code is not implemented yet, not that it's not implemented here. For "not implemented here", you should be using System.NotSupportedException instead.
- Methods that throw System.NotSupportedException should not include other instructions
Simple as it might seem, this rule is a bit tricky to author since the VB compiler has the nasty habit of adding extra instructions (e.g.: return statements) to methods. Also, one needs to keep in mind that a custom message might be provided for the exception, and that message might be retrieved via a method call.
- Throw System.ComponentModel.InvalidEnumArgumentException for an invalid enum argument value
Not really relevant to the rule itself, but it's really annoying that this particular exception lives in the System.ComponentModel namespace rather than System.
- An argument exception should be thrown directly from the method whose argument is being validated
I had hoped that the parameter name verification on Usage.InstantiateArgumentExceptionsCorrectly would cover this issue but, after looking at the rule logic via Reflector, I've decided that a custom rule is going to be need for this one. I'm planning on activating it at the same time as Design.ValidateArgumentsOfPublicMethods (which will definitely be happening before we move to Orcas <gdr>).
- Use recommended exception logging methods
In order to make logged exceptions more noticeable, we decided to create a new helper method that is capable of writing exception details to both a central repository and whatever target location was previously used. The decentralized logging problem was the consequence of an application architecture that you may not share, so this might not be particularly relevant for your team. However, if you do have similarly decentralized logging, you may want to consider a similar approach. If you do decide to add a special exception logging method, then you'll probably also want to add a custom rule to verify that it's being used.
- Review use of System.Diagnostics.Debug methods
Some folks have a tendency to use Debug.Assert or Debug.Fail where they really ought to be throwing exceptions.
- Review catch without throw
Last but definitely not least, this is our "do not eat exceptions" rule. Unfortunately, fixing an undesirable violation of this rule can be very time-consuming, but discovering places where an application is hiding exceptions has tremendous impact on reliability, and the short-term cost is well worth the long-term benefit.
Personally, I'd love to see at least the more generally useful rules from the above list make it into the rule set shipped by Microsoft. I seem to remember seeing something from the code analysis team that promised some new rules around exceptions for Orcas, but I can't find it anymore, and I haven't seen any new exception rules either. It's a pity, because I was really hoping to get rid of my custom rules, particularly the less than ideal verification for other instructions in methods that throw System.NotSupportedException...
1This rule is not available in Visual Studio 2005, but it is shipped with both FxCop 1.35 and Orcas beta 1.