Exploring Existing Code Architecture in Visual Studio

Thanks to the collaboration of...
  • An interactive Dependency Graph,
  • A Dependency Structure Matrix,
  • Interactive Code Querying possibilities,
  • Menus available in the Visual Studio Code Editor Window right-click menus,
  • Menus available in the Visual Studio Solution Explorer right-click menus
...NDepend offers a wide range of facilities to help user exploring an Existing Code Architecture. In this article you'll learn how to benefit from these features in order to achieve most popular Code Exploration scenarios:

Dependency Graph

By default, the NDepend dependency graph panel displays the graph of dependencies between .NET assemblies:



NDepend proposes to explore the graph of dependencies between namespaces of a Visual Studio project.



In the Solution Explorer (or Code Editor window) right-click menu, NDepend proposes to explore the graph of dependencies between types of a namespace. Notice that NDepend comes with an heuristic to try to infer a namespace from a folder in Solution Explorer.



In the Solution Explorer (or Code Editor window) right-click menu, NDepend proposes to explore the graph of dependencies between members (methods + fields) of a type. Notice that NDepend comes with an heuristic to try to infer a type from a source file in Solution Explorer.



▲▲ Go to Top ▲▲


Call Graph

NDepend can generate any call graph you might need with a two steps procedure.
  • First: Ask for direct and indirect callers/callees of a type, a field, a method, a namespace or an assembly. The effect is that the following CQLinq query is generated to match all callers or callees asked.
    from m in Types.WithFullNameNotIn( "NUnit.Core.NUnitException").ChildMethods() 
    let depth0 = m.DepthOfIsUsing("NUnit.Core.NUnitException")
    where depth0  >= 0 orderby depth0
    select new { m, depth0 }
    Notice that, in the CQLinq query result, the metric DepthOfIsUsing/DepthOfIsUsedBy shows depth of usage (1 means direct, 2 means using a direct user etc...). The CQLinq query can easily be modified to only match indirect callers/callees with a certain condition on depth of usage.

    Notice also that callers/callees asked are not necessarily of the same kind of the concerned code element. For example here we ask for methods that are using directly or indirectly a type.



  • Second: Once the CQLinq query matches the set of callers/callees that the user wishes, the set of matches result can be exported to the Dependency Graph. This has for effect to show the call graph wished.



▲▲ Go to Top ▲▲


Class Inheritance Graph

To display a Class of Inheritance Graph, the same two steps procedure shown in the precedent section (on generating a Call Graph) must be applied.
  • First: Generate a CQLinq query asking for the set of classes that inherits from a particular class (or that implement a particular interface). Here, the following CQLinq query is generated:
    from t in Types 
    let depth0 = t.DepthOfDeriveFrom("Microsoft.Scripting.Interpreter.LocalAccessInstruction")
    where depth0  >= 0 orderby depth0
    select new { t, depth0 }



  • Second: Export the result of the CQLinq query to the Dependency Graph to show the inheritance graph wished.



▲▲ Go to Top ▲▲


Coupling Graph

It might be needed to know which code elements exactly are involved in a particular dependency. Especially when one needs to anticipate the impact of a structural change. In the screenshoot below, the NDepend Info panel describes a coupling between 2 assemblies.

From pointing a cell in the dependency matrix, it says that X types of an assembly A are using Y types of an assembly B. Notice that you can change the option Weight on Cell to # methods, # members or # namespaces, if you need to know the coupling with something else than types.



Just left clicking the matrix cell shows the coupling graph below.



A coupling graph can also be generated from an edge in the dependency graph. Here, you can adjust the option Edge Thickness to something else than # type.



▲▲ Go to Top ▲▲


Path Graph

If you wish to dig into a path or a dependency cycle between 2 code elements, the first thing to do is to show the dependency matrix with the option Weight on Cells: Direct & indirect depth of use.

Matrix blue and green cells will represent paths while black cells will represent dependency cycles. For example, here, the Info panel tells us that there is a path of minimal length 6 between the 2 types involved.



Just left clicking the cell shows the path graph below.



▲▲ Go to Top ▲▲


All Paths Graph

In certain situations, you'll need to know about all paths from a code element A to a code element B. For example, here, the Info panel tells us that there is a path of minimal length 2 between the 2 types involved.



Right clicking the matrix's cell and selecting the option Edit a code query that matches paths generatesthe following CQLinq query that matchs all types involved in all paths from type A to type B.
from t in Types 
let depth0 = t.DepthOfIsUsedBy("Microsoft.Scripting.Actions.Calls.InstanceBuilder")
let depth1 = t.DepthOfIsUsing("Microsoft.Scripting.Actions.Calls.CallFailureReason")
where depth0  <= 2 && depth1  <= 2
orderby depth0, depth1
select new { t, depth0, depth1 } 
//--------------------------------------------------------------------
// The type
// public class InstanceBuilder
// - { } Microsoft.Scripting.Actions.Calls
// - Microsoft.Dynamic, v1.0.0.0
// 
// is indirectly using
// the type
// public sealed enum CallFailureReason : IComparable, IFormattable, IConvertible
// - { } Microsoft.Scripting.Actions.Calls
// - Microsoft.Dynamic, v1.0.0.0
// 
// with a depth of 2.


Finally exporting to the graph the 12 types matched by the CQLinq query, shows all paths from A to B.



▲▲ Go to Top ▲▲


Cycle Graph

As we explained in the previous section, to deal with dependency cycle graphs, the first thing to do is to show the dependency matrix with the option Weight on Cells: Direct & indirect depth of use. Black cells then represent cycles.

For example, here, the Info panel tells us that there is a dependency cycle of minimal length 5 between the 2 types involved.



Just left clicking the cell shows the cycle graph below.



We'd like to warn that obtaining a clean 'rounded' dependency cycle as the one shown above, is actually more an exceptional situation than a rule.

Often, exhibiting a cycle will end up in a not 'rounded' graph as the one shown below. In this example, the minimal length of a cycle between the 2 types involved (in yellow) is 12. Count the number of edges crossed from one yellow type to the other one, and you'll get 12. You'll see that some edges will be counted more than once.



▲▲ Go to Top ▲▲


Large Graph visualized with Dependency Structure Matrix

Here, we'd like to underline the fact that when the dependency Graph becomes unreadable, it is worth switching to the dependency Matrix.
Both dependency Graph and dependency Matrix co-exist because:
  • Dependency Graph is intuitive but becomes unreadable as soon as there are too many edges between nodes.
  • Dependency Matrix requires time to be understood, but once mastered, you'll see that the Dependency Matrix is much more efficient than the Dependency Graph to explore an existing architecture. More information on the Dependency Matrix readability can be found in the matrix documentation.
To illustrate the point, find below the same dependencies between 94 namespaces shown through Dependency Graph and Dependency Matrix.







▲▲ Go to Top ▲▲