Developing Application-Wide with NDepend
What? Application-Wide?
When an application reaches a certain amount of code, the application code gets usually partitioned among several Visual Studio solutions. Visual Studio ends up slowing down, especially if it hosts several addins. Above a certain volume of code in the opened Visual Studio solution, the IDE becomes quasi-impracticable. The volume threshold naturally depends on the underlying hardware. For example, on a modern 2000US$ laptop (64bits dual core + SSD + 8GB RAM + Wnd8) one can experiment 20 seconds to open a 50.000 Lines of Code solution. The compile-all time mostly depends on how Visual Studio projects are organized and referenced, but if it is more than 10 seconds one can consider it as a major burden.
So code volume can be a reason to split an application code among several Visual Studio solutions, but there are several other reasons. The most common one is certainly the need to separate Visual Studio test projects from Visual Studio application projects.
.NET developers typically work with several Visual Studio instances opened at the same time. Developing Application-Wide means being able to jump from one Visual Studio instance to another. Thus, developing Application-Wide means making several Visual Studio instances smartly collaborate. Let's elaborate on this feature proposed since NDepend v4.
Concretely
Concretely the NDepend team is facing the multi Visual Studio instances paradigm as anybody else with a sufficiently large code base. Not counting the test solution, the bulk of the NDepend code is spawned on 2 Visual Studio solutions: NDepend.Framework.sln and NDepend.UI.sln. These solutions are made of around 50K logical Lines of Code each. Projects from NDepend.UI.sln depend on projects in NDepend.Framework.sln.
To make Application-Wide a reality, a NDepend project must reference all assemblies built by all Visual Studio solutions (test code assemblies can be included or not, here we chose not).
This NDepend project will constitute the common base that will make several Visual Studio instances aware of each other. The same NDepend project must be attached to each Visual Studio solution.
Now several Visual Studio instances are ready to collaborate. For example if one ask for types that are using the namespace NDepend.Helpers…
…one got some types from several assemblies, including NDepend.UI. We can now choose to jump to a type source code declaration by double clicking it in the result. The declaration will be then opened, in the right Visual Studio instance, actually, the one with the NDepend.UI.sln solution opened. If NDepend.UI.sln was not currently opened, then the type declaration would be opened in the current Visual Studio instance.
The Visual Studio instances collaboration can happen from CQLinq query generated from Solution Explorer, from Solution Navigator, but also, from source code editor directly.
Application-Wide embraces also third-party code
Working Application-Wide means also caring for third-party code declared in referenced assemblies. Controlling third-party code used by your code is pretty important. At production time, third-party code is loaded and executed the same way as your own code. From the user perspective, there is no distinction between a problem coming from third-party code and a problem coming from your code. In the NDepend Class Browser panel, third-party code is blue. Since the initial NDepend project embraces several Visual Studio solutions, querying third-party code will provide a list of code elements across several Visual Studio instances.
Under the hood
Certainly, most software developer used to be a kid that destroyed his toys to see what was the magic inside. So let’s look under the hood about how the things work.
- Primary inputs analyzed by NDepend are assemblies themselves, the ones referenced by the NDepend project, the NDepend project which is common to all Visual Studio solutions. (For a more detailed discussion on NDepend inputs let's have a look at: NDepend Analysis Inputs).
- From assemblies NDepend tries to find corresponding PDB files. A PDB file represents an association between IL code in assemblies and source code declarations in textual source files. PDB files are used primarily by debuggers.
- From PDB files NDepend tries to find source files.
- From source files NDepend tries to find Visual Studio project files.
- From Visual Studio project files NDepend tries to complete the entire set of source files (because not all source files are referenced by PDB files, for example if a file contains only an interface declaration it won't be referenced!).
- From Visual Studio project files NDepend tries to find Visual Studio solution files.
- And that’s it!
Thus, when the NDepend Visual Studio addin is about to open a source file declaration of a code element, it actually knows which Visual Studio solution owns the code element. If the Visual Studio solutions is actually loaded in a Visual Studio instance on the machine, NDepend will choose this Visual Studio instance. The Visual Studio instances collaboration also works across Visual Studio versions (an opening source file request from a Visual Studio 2008 instance can actually open a declaration in a Visual Studio 2010 instance for instance).
We developed several tricky heuristics to recompose all the exposed file structure from assemblies files themselves. The result is that, when attaching a common NDepend project file to several Visual Studio solutions, all living Visual Studio instances becomes aware of each other and can collaborate. Several Visual Studio instance collaboration is a great feature. But another interesting point in embracing application-wide code through a single shared NDepend project file, is that the NDepend static analysis results and tools (CQLinq rules, dependency matrix/graph, 2 snapshots comparison, code coverage results...) also work application-wide. As a consequence, you can harness NDepend static analysis results and tools on the entire application, in each living Visual Studio instance. Concretely you can be informed on a flaw in Visual Studio sln B, from within a Visual Studio instance with Visual Studio sln A opened. Finally, let's notice that the heuristic can be somewhat blurred if some Visual Studio project are shared among several Visual Studio solutions. In that case several solutions can own a same code element. The heuristic will certainly find a Visual Studio solution but at a point in time, this might not be the Visual Studio solution you expected when trying to make several Visual Studio instances collaborate.