I have taken over maintenance of a very large (>2M SLOC) software project, all written in C#. There is very little documentation. I am now wanting to make a change to a module that has public interfaces (about 400), but I don't know what all other modules (there are about 50 total) in the solution may be using this public interface.
How would you create an interface dependency usage tree for a situation such as this? The codebase is too big to simply navigate the Project Explorer and read source code. What tools or methods have you used to create this type of dependency analysis tree?
I do not want to purchase any tools. The Visual Studio tools such as Class View don't seem to handle a project of this size very well. I have thought about writing my own sed/awk/perl-ish script that simply walks the source code and uses pattern matching to build my own dependency/interface useage database, but I don't want to do something the hard way if there is an easy way.
Thanks!
You probably ought to buy something like NDepend.
If there was another FREE tool that would provide similar value, I'd recommend it. However, I truly believe NDepend is your best bet. With a code base of that size, it won't take long for a $400 tool to pay for itself.
I seriously suggest just finding an existing tool to do this. You are never going to be able to get the whole thing sorted out by hand, and the time saved using a tool will definitely pay for the cost of the tool itself.
A guy a work with had this problem - his manager wouldn't pay for a profiler, and so they spent 5 developer days on optimizing the wrong parts of the program. Using the profiler, he found and fixed the problem within half a day.
I know you said you don't want to buy a tool but CodeRush is available fully featured and free for a month (and only $250 after that and you get refactor pro! which is awesome). It replaces Shift+F12 with a really nifty find all references window.
Simple solution to the direct dependencies, Right Click the interface and select "Find All References". You can do this on a per method basis as well.
To find out how much code will break as a result of a change you can add [Obsolete(true,"Testing The change")] to the methods on the interface you plan on changing and trigger a rebuild. This will lead to some parts not building.
Skip to those parts and tag them with [Obsolete(false, "Limited Change")] if you feel this a small change to that class and that you can fix it without any effect on consumers of this class, If you think that instead this will cause major issues to consumers of the class you can tag it as [Obsolete(true, "Cascade")] and deal with its fallout.
Eventually your solution will fully build or you will have so many errors that it will become obvious that the change is so invasive that the only way to really get a handle on the effect is to start trying for real.
The benefit of this approach is that you can do the cascade without needing to actually deal with how you deal with it, just that you need to and a rough evaluation of whether it will trigger consequent changes. Once you are happy you have mapped the change out you have a ready made list of Warnings in you IDE to go and change as you alter the interface proper (and the build really fails).
This relies on not treating warnings as errors, you may have to temporarily slacken you build settings.
Do all this in a fresh source update so you can just roll parts back if you want to try again.
Just as a tip: When you are going to build your own version of such a tool you could also do this on the compiled assemblies using reflection.
.NET offers you all you need to load an assembly and query all interfaces, types, methods and properties in the System.Reflection namespace. This way you don't have to worry about parsing the source code yourself using sed/awk/perl (which is not trivial since you need to resolve namespaces and inheritance).
(Note: What you would not get directly using reflection are assembly dependencies to dynamically loaded assemblies, loaded e.g. though Assembly.Load. You would have to implement a special handling if this is used in you project)
I don't know how your project is setup, but here each module we have has a version number. When we need to make a change to a module, we create a new version. Client code that wants to use the new interface links to the updated module and deletes the reference to old project. With different versions, there are no unintended side-effects from changing an API; client code must explicitly do something.
Additionally, we have a utility that (somehow) crawls over all projects and reports if any of them are using a module that isn't the latest version. It's pretty easy to check (even with Microsoft VSS!) which projects have references to outdated modules.