Thursday, January 08, 2009

CA1502 Microsoft.Maintainability - .NET Compact Framework 3.5 Func to the rescue...

If you are using the Microsoft .NET Compact Framework 3.5 no doubt you have encountered the new Func delegate as luckily for us device devs, Func is supported on the CF. Pretty much the whole of LINQ uses Func’s and lambdas to get the job done.

In addition, if you are using Code Analysis in Visual Studio (FxCop) then you might have encountered the following error: CA1502: Microsoft.Maintainability : Function has a cyclomatic complexity of n. Rewrite or refactor.

You might get this if you have written some code like in this article: http://social.msdn.microsoft.com/Forums/en-US/vstscode/thread/16fdc314-4719-47da-9cb3-2877f29a47b9/

Typical if/else/if/else/if/else drives developers mad. In that link about, the developer has 26 if’s!!

I’m one of those developers that dislikes this type of code. It’s really hard to read and maintain, which is why FxCop is complaining about it.
But as with everything, there is a simple solution and now Func makes this easier for us. I decided to knock up a quick solution to this.

So as per the post above, we have our context class, and the idea behind this code is to set the constructor with a different strategy type based on the passed string (just as the dev requires in the post). So we have our dummy Context class and it looks like the following:
public class Context
{
private readonly Strategy _strategy;
public Context(Strategy strategy)
{
_strategy = strategy;
}

public Strategy Strategy
{
get
{
return _strategy;
}
}
}
Now for the Strategy types, the dev in the article has 26 of these, but for simplicity, I’ve just created 3, but you get the idea, the code is very easily extended:
public abstract class Strategy
{
}
public class Strategy1 : Strategy
{
}
public class Strategy2 : Strategy
{
}
public class Strategy3 : Strategy
{
}

Now for getting the context, I’ve created a static factory class that returns a context based on a string passed to it:
public static class ContextFactory
{
private static readonly Dictionary<string, Func<Context>>
dictionary = new Dictionary<string, Func<Context>>
{
{"1", () => new Context(new Strategy1())},
{"2", () => new Context(new Strategy2())},
{"3", () => new Context(new Strategy3())}
};

public static Context CreateContext(string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException("name");

return dictionary[name]();
}

public Strategy Strategy
{
get
{
return _strategy;
}
}

}
So that's the complete example. Calling the code is done as follows:
Context context = ContextFactory.CreateContext("3");
If you run that code and inspect the Strategy property, it will equal Strategy3.

So lets explain the code abit. Instead of having lots of conditional if/else/if/else statements to figure out what name is equal to, we have created a generic dictionary class, where the key is the identifier that identifies the type we want passed to the Context constructor. The value is to set the the new Func delegate that allows us to execute the code the we want for the passed key. We use the Func so that we return the specified type.

So the line:
return dictionary[name]();
is essentially executing the delegate for the dictionary item "name". We would need to check if the item exists in a real world example and throw a meaningful exception in this case. This was left out to clarify the code.

In addition, FxCop is happy no matter how many items we add to our dictionary.

No comments: