Nitriq Does More Than Just LINQ

Tags: tools, nitriq

Here at NimblePros, we’ve recently begun working to release a new code analysis tool called Nitriq. Because I wasn’t personally involved in its development, my goal was to attempt to break it, as I am wont to do with any new tool. I was pleased to find that it was actually very difficult to throw an impossible query at it -- at the risk of sounding clichéd, “simple queries were easy, and difficult queries were possible.” There are many different code analysis tools, but what we believe differentiates Nitriq is how customizable it is. You aren’t limited to customizing existing rules, and you don’t need to learn any kind of “special” language to create new rules – they’re all written in C#, just like your project.

Nitriq allows you to perform queries on the properties of types, methods, and more, and to issue warnings based on your findings. It’s easy to find methods that require too many parameters, or types that have an inordinate number of dependencies. These queries are normally written as LINQ, because it’s simple to read and write, and idiomatic to a C# developer. Most of the time, you’ll find that LINQ alone can express your rules quite elegantly, but when you start to need more complex queries, you won’t find yourself out on the street.

Nitriq holds its own as your queries get more complicated. Because you’re almost always writing LINQ, it’s easy to forget that you aren’t limited to it when the task at hand requires bigger guns -- you’re really writing an honest-to-goodness C# method, and anything that is valid within method scope in a ‘real’ project can be done in Nitriq as well, because Nitriq rules are methods. This means that you can split your queries into multiple steps to improve readability, or extract methods to simplify queries.

You can’t define a method within a method in C#, but you can define a delegate, and quite easily – really, the only ‘trick’ is that we need to specify the types that are accepted and returned. Types that you’re likely to care about in Nitriq begin with Bf – BfMethod, BfType, and BfNamespace, for example. As a quick first example, let’s extract a simple delegate. We want to get all public methods that are in the core assembly and which are not constructors, operators, or properties.

Func<BfMethod, bool> isRegularMethod =
    method => !(method.IsConstructor || method.IsStaticConstructor ||
    method.IsPropertyGetter || method.IsPropertySetter || method.IsOperator);

var results = Methods.Where(m => m.Type.IsInCoreAssembly)
    .Where(m => m.IsPublic)
    .Where(isRegularMethod);

 

It would also be easy to embed the last part of the query, but extracting parts of a query can be convenient and improve readability if they occur in multiple locations in your query, or, like in this case, the expression is very long. There are also cases where using this method will allow you to express rules that would be difficult or impossible to express if you were limited to a single LINQ expression.

For a slightly more complex example: let’s say that you’ve inherited the source of a large, egg-related library with an base class called, appropriately, Egg. There is a naming convention that the names of all of Egg’s overly-specific derived types must be suffixed with “Egg.” Not all eggs directly inherit from Egg, though: you have many types of BirdEgg, many types of LizardEgg, and even a couple of MammalEggs, thanks to our friends the monotremes. Furthermore, we can’t assume anything about the depth of our inheritance hierarchy – a ChickenEgg inherits from BirdEgg directly, but an AracaunaEgg is a kind of ChickenEgg. To make matters more difficult, Eggs do not currently implement an interface, so we can’t do a type.Interfaces.Any(t => t.Name == "IEgg"), and the Egg namespace is polluted with a variety of Nests, so we can’t apply our rule to every type within the namespace. You need to find a list of all descendants of Egg – no matter how indirect – that do not end with “Egg.”

A type’s DerivedTypes field includes only its directly derived types, but because we can break the query down, it doesn’t have to be difficult to find further derived classes. Using a delegate, we can use recursion, which will make it much easier to express this query.

Func<BfType, System.Collections.Generic.IEnumerable<BfType>> allDerivedTypes = null;
allDerivedTypes = delegate(BfType type)
{
    if (type.DerivedTypes == null || !type.DerivedTypes.Any())
        return new BfType[]{};
    
    var furtherDerivedTypes = type.DerivedTypes.SelectMany(t => allDerivedTypes(t));
    return type.DerivedTypes.Union(furtherDerivedTypes);
};

var results = Types
    .Where(t => t.Name == "Egg")
    .SelectMany(t => allDerivedTypes(t))
    .Where(t => !t.Name.Like("Egg$"));

WarnGreaterThan(results, 0);

Our rule can now help us easily find that somebody forgot to put Egg at the end of several classes, causing the humble Ostrich and many other excellent birds to have rather unexpected methods and properties such as BreakShell() and ShellStrength.

Ostrich

Nitriq has allowed us to save time by automating a search that would normally be cumbersome and error-prone, even with excellent tools like ReSharper. Chances are, your application doesn’t have any eggs in it – but if I can write a rule specifically about eggs, of all things, you can write rules to handle whatever your specific requirements happen to be.

There are many other things you can do with a code analysis tool like Nitriq, which I plan to blog about occasionally, as do my alphabetically-by-surname-ordered coworkers, Scott and Ben. Nitriq isn’t quite ready for mass-consumption, but if you’re feeling adventurous, and don’t mind the occasional hiccup, there’s a free beta available.

Add a Comment