> > Once we start designing our systems with an eye to
> > controlling interactions and dependencies, we can start to
> > reduce the complexity of each individual "component" to
> > the point that it can be understood [...]
> > [...] a "tool that imposes
> > [some] constraints" isn't a deficiency, it's a aide.
> > Those
> > "constraints" are exactly the language features that the
> > developer uses to express the independence and
> > inter-dependence of the components in the system.
>
> I agree with you in principle but I have a problem with
> the kind of solutions that are assumed to be helpful in
> managing dependencies. There seems to be a general
> assumption that the stricter you define interfaces and
> contracts between components, the better you can handle
> dependencies.
>
> Very often, no distinction is made between different kinds
> of dependencies. [...]
> A tool that imposes one very strict way of managing
> dependencies may solve one class of dependency problems
> while creating lots of new problems for other change and
> dependency patterns. And that's why I prefer tools that let
> good software engineers pick and choose what suits the
> situation best. [...]
> I don't want to be forced into a
> style of coding that assumes I'm creating the final
> version of an interface that a large global committee is
> about to cast in stone for the next 5 years when in fact
> I'm just prototyping the innards of some future in house
> application.
I couldn't agree more. There are (at least) two quite separate regimes of dependency/interaction control. Each requires a different dependency/interaction management style, and each may be best implemented with different tools (aka languages). BTW, I think it's the fallacy of "one size fits all" (Java *or* C++ *or* Perl *or* Eiffel ...) that gets us into many of these discussions.
One extrema regime is the "heavy weight" published (and I use that in the sense that Martin Fowler does in
http://martinfowler.com/ieeeSoftware/published.pdf) interface; perhaps a better term is "inter-component interface". In this situation you need to bring to force the full weight of DBC, information hiding, etc. because this interface needs to stand the test of time (and varying implementations over time). Here you want to use tools that force you to protect yourself from inadvertent (or malicious) abuse of the intended interface/contract that you have published. This is a two-way street, BTW. The component's writer needs to be prevented from inadvertently changing the promised behavior (i.e. the interface/contract), *and* the component's clients need to be protected from accessing unpublished features of the component, as well as getting different behavior from two implementations of the same interface/contract.
The regime on the other end of the spectrum is very "light weight". In this case the context of the dependencies and interactions is very local (small), so the complexity for even an n*m case isn't too big. The most extreme case of this is the relationship between private attributes and the code within a single class. A slightly larger example might be a package of tightly coupled classes fronted by a single published Facade (GOF) class. In this situation the implementer should be allowed free rein to manage his/her own internal dependencies. In fact, working behind a tightly controlled published interface/contract opens up the possibility for vastly relaxed intra-component dependency/interaction management (bring on the global variables!). A perfect example of this is the case where performance optimizations force tightly-coupled code (perhaps even tightly coupled to a specific piece of hardware). Protected by a strong published interface/contract, this implementation can be safely interchanged with other implementations, and all of them could be used by a myriad of clients ("m" clients + "n" component implementations).
Cheers,
Geoff S.