Volume 30 Number 7
Visual Studio 2015 - Analyzing Architecture with Code Maps in Visual Studio 2015
By Blair McGlashan | July 2015
Improving the architecture of your application is crucial for preventing the build-up of technical debt, and for maintaining good coding velocity. Further, getting a quick understanding of the impact of a potential code change is an important aspect of deciding whether the change makes sense, and if so, what its likely cost will be. Both goals require an ability to understand the architecture of your application and analyze the dependencies surfaced there. To tackle the first goal, you typically work from the top and drill down. To tackle the second, you generally work from a specific code element and expand up.
In this article, we’ll show how Code Map, enhanced with new capabilities in Visual Studio 2015 Enterprise, can be used to:
- Understand the overall architecture of a .NET application.
- Analyze dependencies surfaced in that architecture by progressively drilling into the details.
- Understand and analyze the impact of a proposed change to the code by building a dependency map from a specific code element.
We used two examples for illustration. For top-down architecture and drilling in, we used the “Roslyn” code base (the .NET Compiler Platform). We chose this for two reasons. First, it’s large so understanding the overall architecture isn’t so easy. (In fact, it’s so large you typically have to wait for a few minutes from a cold start to create the initial diagram, while the initial build and indexing take place. Performance is much better with smaller solutions.)
Second, Roslyn is open source, so you can easily try this for yourself. To analyze the impact of a proposed change, we used an experimental prototype from Microsoft for visualizing a backlog in Team Foundation Server (TFS). This isn’t publically available, but we also identify a code element in the Roslyn code base you can explore in a similar way, though the reason for doing so in that case is less clear.
We used the RTM version of Visual Studio 2015 Enterprise edition, which can be downloaded from bit.ly/1JbLA4S.
Understand Overall Architecture
Our starting point was the Roslyn solution opened in Visual Studio, and already built. To get there, we cloned the Git repository from https://github.com/dotnet/roslyn, using the Git cloning experience on the Connect tab of the Team Explorer window of Visual Studio, and then opened the RoslynLight.sln solution, which was automatically found on opening the cloned repository. Then we ran the src\.nuget\NuGetRestore.ps1 script from the Developer command prompt for Visual Studio 2015 to restore the NuGet packages required to build the solution. (Execution of Windows PowerShell scripts is disabled by default. Enabling script execution requires an elevated command prompt. If you’re not familiar with Windows PowerShell, you might find it easier to run the script by right-clicking and choosing Run with PowerShell from Windows Explorer.)
Next, we added the xunit.runner.visualstudio NuGet package to the solution so that the Roslyn solution, which are xUnit tests, were detected by Visual Studio. Finally, we built the solution (this can take some time). To get an immediate understanding of the architecture of this code base, we selected the Generate Code Map for Solution without Building option from the Architecture menu. This created a map that shows a graph of the solution structure and project references. In particular, seeing the references helps you understand how the various parts of the solution are related in a way you simply can’t appreciate by looking at the solution, and would take a lot of exploration of project references to figure out.
Once all the binaries have been indexed into the code index (essentially a SQL database storing the full logical structure of the code), the map transforms into what you see in Figure 1. The new map displays much richer dependencies, color-coded according to the Legend (click the toggle button in the Code Map toolbar to view), whose thickness reflects the degree of dependency between the related components. For example, a green link indicates inheritance (and possibly other dependencies) between classes in the related projects. The darker-green nodes represent test projects.
Figure 1 Architecture Map After Indexing
The filters on the right can be used to exclude different kinds of dependency. For example, you might want to view the diagram with all test code hidden, or with all links hidden except inheritance and implements links. You could also hide generic nodes and delete any node containing referenced assemblies that are external to the solution. This makes it easy to see where inheritance relationships exist across the architecture of the product code (tests are not counted as product code).
For example, looking at the roots of the Inherits From and Implements relationships gives you a sense of the conceptual abstractions in the framework. You can see that the Compilers framework is at the base of everything else. Within the compilers framework, the Core is also at the base, although there are also some classes in the Core that seem to derive from the CSharp and VisualBasic layers, which is, perhaps, a hint that the layering isn’t quite right. (We’ll show how to investigate this further in the next section.)
You can zoom in on areas of the diagram to explore further, and even select a node or nodes (control-click enables a multiple selection) to explore on another diagram using the New Graph from Selection context menu command. For example, Figure 2 illustrates the result of doing this for the Core and CSharp subgroups of the Compilers part of the framework. Bottom-to-top layout has also been applied to the result in order to have base types at the top.
Figure 2 Zooming in on the Inheritance Hierarchy
Analyze a Dependency
As you can clearly see in Figure 2, the Core inherits some classes from CSharp and VisualBasic, which is a bit of a surprise. Let’s take a look at how to drill into this further to see what’s causing the anomaly. We started by selecting the dependency link (between Core and CSharp) and choosing Show Contributing Links on New Code Map. The result is shown in Figure 3.
Figure 3 Result of Drilling into the Link Between Core and CSharp
We saw that one class is at fault, and had to wonder whether, in fact, the VBCSCompiler.exe project/assembly should really be part of the Core grouping. Perhaps we needed to refactor the Solution Folder structure to move it out. But before making that change, we used Code Map to explore the potential impact. Going back to the map shown in Figure 2, we could re-enable all the link filters in order to see all assemblies, and edit the diagram by moving the VBCSCompiler.exe node from Core to CSharp, to see the impact on dependencies (see Figure 4). This certainly appeared to clean things up, though we then found that the Roslyn.Compilers.CompilerServer.UnitTests.dll also seemed to be in the wrong place. Exploration can continue in this vein. You can even create new group nodes on the diagram to produce a cleaner architecture, which can then be used to inform refactoring of solution folders, as well as deeper refactorings, such as splitting classes or interfaces between assemblies.
Figure 4 Moving Nodes to Explore the Impact of Potential Refactorings on Dependencies
Analyze the Impact of a Proposed Change
You’ve seen how to examine the architecture and dependencies of a code base from the top down, and how that can help you identify dependencies to analyze further. You also saw how Code Map can be used effectively to do that analysis by progressively drilling into lower levels of detail. Now we’re going to change tack and look at how the same capabilities can be used to explore dependencies with a bottom-up approach, starting from a code element. In particular, we’ll identify the set of dependencies that reference that code element and might be impacted when the code element is changed.
Our example shows code from an experimental prototype from Microsoft that’s used to visualize and make changes to a backlog in Visual Studio Online or TFS using hierarchical Kanban boards. In this example, we wanted to add the ability to change the title of a work item directly through the map, which requires the addition of a new enumeration literal to the ChangedProperty enum. (If you want to try this with an Enum in the Roslyn code base, you can use Microsoft.CodeAnalysis.LocationKind.) First, however, we wanted to explore the impact of making that change, so we clicked on the reference’s CodeLens indicator, and then clicked on the Show on Code Map link.
You can see the result in Figure 5, which presents all the methods and other class members that reference the ChangedProperty enum in some way. This diagram shows the members in architectural context; that is, grouped by class, namespace, assembly and solution folder. We then used the filters to eliminate Solution Folders and Assemblies and changed the Layout to a left-to-right format.
Figure 5 The Reference Dependency Map After Hiding Solution Folders and Assemblies and Laying out Left to Right
This map can now be used to explore all the areas of the code that might be impacted by this change. Not only can you use the map to navigate the code—double-clicking on a node brings you to the correct place in the code—you can also use it to see patterns in the design. So, as Figure 5 shows, you can immediately see that the method SortAndReparent appears in classes of the StoryMaps.ViewModel namespace, and a quick inspection of the code of any of those classes reveals that no code change is required. However, when you look at the Process method in the WorkItemsWriter class, you can see immediately from the diagram that it calls out to sub-processes, one for each literal in the ChangedProperty enum. Adding a new literal is likely to mean that a new sub-process method will be required. Inspection of the code confirms this suspicion.
As you go through this process, you can make nodes green, red or some other color to indicate whether follow-up is required (use the Edit submenu of the context menu | Flag for Follow-up), as well as add comments to the diagram with more detail (see Figure 6).
Figure 6 Annotated References Dependency Map
Understanding the architecture of your application is important, and so is knowing the impact of any potential code change.
In this article, we showed how to use Code Map, enhanced with new capabilities in Visual Studio 2015, to understand and analyze the overall architecture of your .NET app, and examine dependencies at a high level. These capabilities include:
- A much-improved top-level diagram generated from a solution, which uses solution folders to provide an initial grouping of nodes, and styles project nodes according to their type.
- The ability to create new code maps from a selection on an existing map.
- Node and link filtering, in particular the ability to filter out tests and different kinds of links.
We then showed how drilling into a dependency link, enhanced in Visual Studio 2015 to make use of link filters, can quickly unearth the cause of an unwanted dependency, right down to the line of code where the dependency is introduced.
Finally, we showed how you can take a bottom-up approach with code maps to reveal the impact of a proposed code change by exploring the dependency map created from the reference’s CodeLens indicator from a specific code element, and then using comments and flags in the diagram to record the results of analyzing the impact of making the change.
Stuart Kent is a group program manager at Microsoft responsible for developer experiences in Visual Studio and Visual Studio Online, focused on controlling technical debt and code sharing and collaboration. This includes architecture analysis tools, aspects of CodeLens and code search.
Jean-Marc Prieur is a senior program manager at Microsoft envisioning and driving the delivery of experiences in Visual Studio and Visual Studio Online focused on controlling technical debt, including architecture analysis tools.
Blair McGlashan is an engineering manager leading a team based in Cambridge, U.K., delivering experiences focused on technical debt, including architecture analysis tools.