Understanding Code: Static vs Dynamic Dependencies
Patrick Smacchia (NDepend) has a good post about this topic. I have an issue with his assertion that "Static Structure is the Key":
The idea I would like to defend now is that when it comes to understand and maintain a program, one need to focus mostly on the static dependencies, the ones found in the source code. Knowing dynamic dependencies (who calls who at runtime) can make sense for example to profile performance or to fix some particular bugs. But most of the time invested to understand a program is generally spent in browsing static dependencies.
The problem is that this doesn't really hold for applications that were written with a container in place. Let us take Rhino Service Bus as an example of that. If you try to follow the static dependencies in the code, you will not be able to understand much. There aren't many.
If we will look at things like error handling, administration tasks or time outs, all of them are key parts of the way certain aspects of RSB behave, we will see that there is never a static reference to them. Instead, they are pulled in as a set of generic components that know how to integrate into the bus.
A significant part of the actual behavior in RSB is dynamically configured by RhinoServiceBusFacility, and even here, I felt that this class was getting too many responsibilities, so we broke that apart to more dynamically composed parts. The configuration syntax, for example, is driven by the facility and by the BusConfigurationAware components (of which we currently have three).
Focusing on the static dependencies in RSB wouldn't be very useful, not a lot is happening there.
Comments
I think that the Patrick assertions are right when you consider that in general the effort to reduce the dependencies in a project concerning the work that each member (developer) has to do every day. Of course if you are working on an application framework, as RBS may be considered, the prospects are very changeable.
Everyone who writes enterprise level applications should use IoC patterns to prevent static dependencies. With a tool like ReSharper you can create a interface with a mouse click. Use a IoC framework like Windsor Containers, Unity, NInject, Spring.net and you have a static dependency less.
In my opinion abstracting is kind a useless when you still have static dependencies. So yes, it may be easier to change a certain implementation, but it still requires a code change.
A 'good' example how IoC is abused is misunderstood is Microsoft SCSF. Modules are indeed using interfaces to communicate with, but the module initializers decide which implementation is used.
Our CAB framework is using a WCF service for configuration and updates. This prevents that end-users have change (and theirfor need to understand) config files so the oracle NHibernate driver is used in stead of the SQL2008 driver.
@ Dave:
The way I currently work is make it work with static dependencies, then refactor to an IoC container (Unity is my flavour of the month). I've taken on this approach in many parts of my development, from the interface (make it work without ajax, then add the flavour) to code.
"In my opinion abstracting is kind a useless when you still have static dependencies. So yes, it may be easier to change a certain implementation, but it still requires a code change."
So it's not useless, it makes implementation easier. One thing I personally have to keep in mind constantly is to not abstract everything right away. Only abstract when needed, else you risk creating a true 'enterprisey' application. Could also be my inexperience with IoC containers though.
Apologize for the responding delay, I missed this post.
As Tommaso pinpoint I think your example is a bit biased, a bus component is by its very nature here to avoid static dependencies between serviced components. So if the bus implementation itself has few static dependencies it is certainly a good think (more extension points).
But in the general case, too many dynamic dependencies is time-consuming, often useless and makes a program hard to understand while not enough dynamic dependencies makes a program monolithic, hard to maintain.
Patrick,
This isn't the case in most of the applications that I build.
If we take NH Prof as a good example, the entire system is build as a set of pluggable components that come together to create some common behavior.
Rules and analysis are a good example of that, but I am also talking about things like deciding when a session is started and ended, or how to detect a SQL statement.
Having large number of dynamic dependencies can certainly make a system more complex, _if you don't have conventions_.
If your system is full of special cases, then you wouldn't be able to understand it. If it is convention based, you have very few conventions, and you are done.
Personally I am a big adpet of the tenet:
fool me once, don't fool me twice
It means I avoid creating abstractions to serve one implementation now and maybe some others in the future. Notice that it doesn't mean I never create abstractions since the beginning.
The nice consequence of applying this tenet is that I constantly have the minimum amount of abstractions needed in my code. The other consequence is that abstractions created this way are very relevant: I am often pleasantly surprised to figure out how well old crafted abstractions serve nicely new needs I haven't forecasted.
Patrick,
That is funny, I have the same exact tenet, that is why I start from the abstraction :-)
Comment preview