Tuesday, December 28, 2010

QueryMyDesign allows you to query the structure of your own code.

What if you could test your design like this:

from m in Methods.InAssemblyOf<MyClass> where m.CyclomaticComplexity() > 40 select m

or this?:

from t in Types.In(sut) where t.CountUsesOfNamespace("SkinnyDip.Tests") > 0 select t

Then, if placed inside a unit test method, you will be able to verify your design (and ensure against regression):

Assert.Empty(from t in Types.In(sut) where t.CountUsesOfNamespace("SkinnyDip.Tests") > 0 select t);

Why re-invent NDepend? Short answer, I’m not. NDepend can do anything QueryMyDesign can and much much more using its own query language. I just thought it would be interesting to have a small, simple api to express basic structural queries with a LINQ friendly syntax.

This way, you get type safety for free. Plus R# refactorings will work all the way to your queries.

Did I say type safety? Since namespaces are represented as strings by Cecil and since they're not really first class citizens of System.Reflection either, working with namespaces is not nearly as fool-proof as working with types. C# has the built in function typeof(A) but I miss namespaceof(A) and methodof(A.B). Methodof(A) can be faked by using Clarius Labs’ Typed Reflector and the Reflect<MyClass>.GetMethod(c => c.DoStuff()) syntax. I’ve modified the code to return Mono.Cecil.MethodDefinition instead of System.Reflection.MethodBase.

This allows you to write tests of single methods:

Assert.True(Reflect<D>.GetMethod(d => d.UsesE()).CyclomaticComplexity() < 10);

So, what can you learn about your code using QueryMyDesign?

* Number of instructions, number of variables, and cyclomatic complexity of methods, types, namespaces and assemblies.

* All references (methods to methods, types to types, namespaces to namespaces and assemblies to assemblies.)

* Discover cyclic references between types (and the reference graphs between them.)

* Calculate Uncle Bob's metrics such as Instability, Abstractness, Distance From Main Sequence, Amount of Pain, and Amount of Uselesness.

Convienient syntax

If you want to ask specific questions, like “what types does System.String use?”, “does type A depend on type B?” or “Which of my types has any cycles?”, there’s convienient extension methods provided:

var stringDependencies = Types.UsedBy<string>()
bool aUsesB = Reflect<A>.GetType().FindUsesOf<B>().Any();
var typesWithCycles = Types.InAssemblyOf<MyType>().Where(t => t.HasCyclicDependency());

Behind these easily accessible methods lie classes such as MethodDependencyFinder, TypeDependencyFinder and so on. For more advanced scenarios you’ll need those.

Pain and uselessness

If you want to get dirty with the metrics relating to The Main Sequence, things get a little bit less convienient. But only slightly. Metrics like Instability needs to know of both incoming and outgoing dependencies so we need some way of defining the system boundary. Otherwise we won’t be able to find incoming dependencies. We need a Dependency Structure Matrix:

var dsm = new TypeDependencyStructureMatrix(new[] {typeof(SomeClass).Assembly});

This constructor takes a collection of assemblies to search within. When that’s  settled, we can ask questions like:

double i = dsm.GetInstability<SomeClass>();
double a = dsm.GetAbstractness<SomeClass>();
double d = dsm.GetDistanceFromMainSequence<SomeClass>();
double p = dsm.GetAmountOfPain<SomeClass>();
double u = dsm.GetAmountOfUselesness<SomeClass>();

Whether these metrics can tell you precisely what parts of your code hurt is a different discussion. At least they’re easily accessible using QueryMyDesign.

Get the source at GitHub. And remember, it’s alpha quality.

No comments:

Post a Comment