The red pill
When we code something in C#, we use object-oriented software construction techniques. That means, our implementation takes the form of classes and the relations between them. But that doesn’t really say it all, because as we develop, a mental model gets built as well. This is our idea of how the system works, and generally contains all the big stuff – that is, the major classes and how they work together. Small details may be lost in this model, to be recovered later as we once again experience the implementation through the debugger.
When we look at the code six months after we’ve written it, or when we look at other people's code, this mental model may not be present. We have to start building it up based on inspection and, usually, more experience in the debugger. A direct transfer of the mental model is definitely a superior solution in this instance. How can we transfer something that is only in the original author's mind?
So, as we look at this, it becomes clear that C# has obvious rules and constructs. We can leverage these to create a visualization. And while this generated version cannot match the mental model in terms of the importance of types and their usages, it could approximate it based on the breadth. That is, the number of properties, the number of methods, and the way those methods and properties are scoped could be used to represent a type visually. Further, isolating the references between types could show exactly where two types have dependencies. While still not as good as the hand-crafted mental models that most of us carry around, these generated images could yet give us an at-a-glance idea of type functions and relations. This is the goal of the MiniWalker.
The visualization convention we use here is called the Mini Diagram. Designed specifically for the visual representation of C# types and their relations, this convention lends itself to generation. And, we know you are probably thinking of UML! But UML was designed to represent any language, and is intentionally implementation independent. It, therefore, hides the key information critical to understanding C# types. The Mini Diagram convention goes in the opposite direction, adding descriptive graphic details wherever possible. If you really want UML, just use the Class Viewer in Visual Studio 2005. For our purposes, UML falls far short of the mark.
Specifically, here we need to see all the fields, methods, properties, and events, not just the public members of the type. And most importantly, we need to map the dependencies in these members so that we can literally see the level and intensity of the dependency. Why? Because, it lets us see how the types are working, without reading every single line of the source. It also gives us a helicopter-view of the type quality - types with many references are either extremely high-level, or perhaps just in need of a refactor. And finally, Minis give us an at-a-glance feel of the type purpose.
For example, "controller" types generally have fewer
public members and lots of
protected methods. Like this:
You can also tell when a "controller" type leans towards fields instead of parameters while communicating between methods. Like this:
"Data" types are usually the opposite, having almost all the
public properties and little else. Notice how the
private field for a property appears in the property oval, accentuating the message that this type has little real internal logic:
As for type relations, the presence of parameter icons (we call these little squares, kibbles) usually tips us off to the fact that a type is passed as a parameter. Like this:
If there are no parameters, then clearly, the method creates the type:
The point is that, after a few minutes of playing with Minis, you start to get used to them, and can spot the essence of a type in a split second. You're getting oriented in your own (or someone else's) source, and you're doing it quickly. As we move forward with the MiniWalker, we will be making enhancements to heighten this effect.
Now, we’re not in this for an academic exercise. We actually have quite serious, and sometimes, even ponderous, requirements to handle. So, the purpose of MiniWalker is to be a fully-functional and highly useful tool which helps us see clearly and quickly, exactly where our implementations are going, and where they are falling short. The first and foremost in reaching this goal is the completion of a high-quality parser. We need a parser because only a custom parser can dig down to the level of detail necessary to build useful visualizations. For example, the parser "figures out" the following:
- All types declared in the current solution.
- Type references: field types, property types, parameter types, and types created in the body of a method or the declaration of a field.
- Leveling: higher types and lower types are determined through type creation.
Leveling is a key concept of MiniWalker. We consider types like
Rectangle to be "low-level" types. We think of these as "closer to the metal". Types like
ListView are considered "high-level" types, because they leverage many other types to get the job done. Determining the type level is not that easy, and we are still working on it. The basic rule we use is that if one type creates another, the first type is higher than the second. This works out for some cases but not for all.
Setting up the MiniWalker
First, you need to download and unzip the prototype. If you accept the defaults, it will unzip to C:\MiniWalker2005. You can point the MiniWalker to your live source solution, or point it to a directory with a copy of your source. Within the unzipped default directory, there is a nested directory named CopyYourSolutionHere. To use the default behavior of the MiniWalker, you should copy the solution and the related projects of the source you want to walk, into this directory. Otherwise, click the "Search for Solutions" button in the Solution workspace to pick the directory where your solution is, anywhere on your local hard drives.
Next, run MiniWalker.exe. The application will immediately search for solutions in the chosen directory and begin parsing them. If it is successful in parsing your solutions, upon completion of all parsing, the first solution will be selected and the Type workspace displayed.
The solution workspace
The first screen you will see when you run MiniWalker is the Solution workspace:
Here, all of the solutions found are displayed, along with the parsing progress. When the parsing completes, the first solution is automatically selected and the Type workspace is displayed. You can return to the Solution workspace at any time, via the switch control:
Walking the Minis
All of the types in the solution are shown in the Type workspace. Select a type to see the MiniDiagram for that type:
The results of leveling are displayed directly in the Mini Diagrammer itself. Here, higher level types appear on the left, and lower level types on the right:
Clicking either of these lists causes all of the references between the types to be drawn as a series of blue arrows, with the targeted member bodies highlighted and labeled:
When a type is selected from the list, a small blue icon appears above its Mini Diagram:
Clicking this icon hides the Mini and displays the list once again. You can then select a different type. When a selected type has more relations than could be explored, a blue arrow is displayed on the left side (or right side) of the Mini:
Clicking this arrow causes the Minis to slide to the left or right. This is called "walking" the Minis. It’s kind of a "drill-down" through the various relations of a type. Of course, if you are moving to the left, you are actually "drilling-up", moving to higher level classes. This walking procedure, and the information you get from it, is the essence of the MiniWalker experience.
Source highlighting helps you see the implementation behind any property, method, event, or reference depicted in the Mini Diagram. You can inspect the members of a Mini by positioning the mouse over the member body:
Click a member to see its source:
When references are displayed like this:
they are highlighted in the source like this:
BigArrowColors in its constructor and in a property. Clicking the constructor or the property would highlight that member:
You can then see the reference highlighted inside the member:
The reference pad
The reference pad is a list of types which appear above the source of the selected type. When clicked, the types listed here become the selected types. The color-coding of these types is as follows:
- White: Ancestor or interface.
- Black: Descendant.
- Dark blue: Higher level classes (types which create the selected type).
- Electric blue: Lower level classes (types created by or referenced by the selected type).
- Gray: External (types in the referenced assemblies).
You can limit the types displayed, by entering the number of references the type should have, quickly isolating the types with the most references:
The Type workspace uses a new kind of control called a
GridBag divides the surface of the application into collapsible rectangular areas. The gap between these areas contains invisible splitters. You can move your mouse over these gaps, and the cursor hinting will indicate the splitting action that would occur during a drag. You can also click in the gaps to collapse the adjacent area (effectively hiding it).
(Click here to return to the article.) Here is a full description of the Mini standard, though the prototype does contain a complete legend as well. The heart of the Mini diagram is a rectangle representing the object:
Properties are represented by an ellipse:
If a property implements a value type, it contains a tiny rectangle:
If a property implements a reference type, it contains a circle:
The getter for a property is indicated by a shape on the left side of the ellipse:
The setter for a property is indicated by a shape on the right side of the ellipse:
When a getter or setter contains any code beyond a simple return or an assignment of the field value, a line is drawn from the enclosed shape to the edge of the ellipse. Here is an example of a read/write property with code in the getter:
Methods are indicated by rounded rectangles:
Tiny squares are used to represent the parameters of a method:
Methods with more than three parameters use an ellipsis in place of the third square:
Interfaces are shown at the top right via a prong-like symbol:
Events are displayed at the bottom via an outlet-like shape. The down arrow inside the shape represents the delegate used to implement the event:
Fields containing value types are represented by tiny rectangles – these look almost like a hyphen:
Fields containing objects (reference types) are displayed as circles:
Constructors appear at the top left. Like methods, these shapes contain tiny squares representing any parameters:
A destructor, if declared, is displayed as a grey rectangle immediately below the last constructor:
The scope and visibility of the various object parts are indicated by the location in the Mini. Public properties appear on the left, with the left tip of the ellipse protruding:
Public methods appear on the right, with the right edge of the rectangle protruding:
Public events appear at the bottom, with the "outlet" protruding:
protected members are displayed in special regions inside the Mini. Here, we have a Mini with a static region, a private region, and a protected region. The static region at the top contains fields. The private region in the middle contains fields and methods. The protected region near the bottom contains methods and properties:
- October 10th, 2005 - Article posted.
- October 25th, 2005 - Now maps your C# code.
- November 2nd, 2005 - Parses C# 2.0.
- November 3rd, 2005 - Source highlighting.
- December 1st, 2005 - More support for .NET Framework 2.0.
- May 4th, 2006 - Source directory picker, updated parser, more colors for non-digital displays.
- June 7th, 2006 - Updated source code parser.