10

A bit of background: As a team lead I use NDepend about once a week to check for the quality of our code. Especially the test-coverage, lines of code and cyclomatic complexity metrics are invaluable for me. But when it comes down to levelization and dependency cycles I am a bit ... well concerned. Patrick Smacchia has a nice blog post which describes the goal of levelization.

To be clear: Under "dependency cycle" I understand a circular references between two namespaces.

Currently I am working on a Windows CE based GUI framework for embedded instruments - just think of the Android graphics platform but for very low end instruments. The framework is a single assembly with about 50.000 lines of code (tests excluded). The framework is split into the following namespaces:

  • Core Navigation & Menu Subsystem
  • Screen Subsystem (Presenters / Views / ...)
  • Controls / Widget Layer

Today I spent the half day on trying to bringe the code to proper levels [thanks to Resharper no problem in general] but in all the cases some dependency cycles exist.

So my question: How strictly do you follow the "No Dependency Cycle" rule? Is levelization really that important?

2
  • 2
    If "no dependency cycle" means no circular references, then - no excuses. Those are pure evil. Mar 21, 2011 at 21:03
  • @ollifant: define what you mean exactly when you write "No Dependency Cycle". Between layers or between classes within a layer?
    – Doc Brown
    Mar 21, 2011 at 21:07

5 Answers 5

9

I recently wrote 2 white books, published on Simple-Talk about the topic of architecturing .NET code (1st book is about .NET assemblies, 2nd one about namespaces and levelization):

Partitioning Your Code Base Through .NET Assemblies and Visual Studio Projects

Defining .NET Components with Namespaces

Is levelization really that important?

Yes it is!

Quote from the 2nd white book:

If the graph of dependencies between components contains a cycle, components involved in the cycle cannot be developed and tested independently. Because of this, the cycle of components represents a super-component, with higher entropy than the sum of the entropies of its contained components.

(...)

As long as the acyclic components constraint is continuously respected, the code base remains highly learnable and maintainable.

  • In traditional building architecture, the gravity strength put the pressure on low level artifacts. This makes them more stable: 'stable' in the sense they are hard to move.
  • In software architecture, abiding by the acyclic component idea put the pressure on low level components. This makes them more stable, in the sense that it is painful to refactor them. Empirically abstractions are less often subject to refactoring than implementations. it is, for this reason, a good idea that low-level components contain mostly abstractions (interfaces and enumerations) to avoid painful refactoring.
6

I never allow circular dependencies between classes or namespaces. In C#, Java, and C++ you can always break a circular class dependency by introducing an interface.

Coding test-first makes it difficult to introduce circular dependencies.

4

It's always a tradeof : you have to understand the cost of dependencies to understand why dependencies are to be avoided. In the same way, if you understand the cost of a dependency, you can better decide if it's worth in your specific case.

For example, in console video games, we often rely on dependencies where need close relationships of informations, mostly for performance reasons. That's fine as far as we don't have to be as flexible as an edition tool for example.

If you understand the constraints in witch your software have to run (being hardware, OS, or just design (like "the simpler UI we can")) then it should be easy to select understand wich dependencies should not be done and wich one are ok.

But if you don't have a good and clear reason, avoid any dependency you can. Dependent code is debug-session's hell.

3

Whenever this topic is discussed, the participants usually lose site of the distinction between build-time and run-time cycles. The former is what John Lakos addressed as "physical design," while the latter is basically irrelevant to the discussion (don't get hung up on run-time cycles, such as those created by callbacks).

This said, John Lakos was very strict about eliminating all (build-time) cycles. Bob Martin, however, adopted the attitude that only cycles between binaries (e.g. DLLs, executables) were significant and should be avoided; he believed cycles between classes within a binary are not terribly important.

I personally hold Bob Martin's view on this. I do, however, still pay some attention to cycles between classes because the absence of such cycles makes the code easier for others to read and learn.

It should be pointed out, however, that any code you build with Visual Studio is not capable of having circular dependencies between binaries (whether native or managed code). Thus the gravest problem with cycles has been solved for you. :)

1

Almost totally, because build-type dependency cycles are inconvenient in Haskell, and impossible in Agda and Coq, and those are the languages I typically use.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.