开发者

Are circular references ever necessary?

I've inherited a Visual Studio Solution that contains numerous circular references between Projects.

Is there ever a situation where this is remotely acceptable开发者_JAVA百科?

Just trying to confirm my suspicion that this application is designed horribly. Thanks in advance.


I once read a column where they compared 3 kinds of models: the Spaghetti model, the Lasagna model and the Ravioli model.

In the Spaghetti model, all the code is interlinked with each other, there is no clear structure. It's horrible, and we probably can all agree on that.

In the Lasagna model, the code is divided in different layers, and only a higher-level layer can access a lower-level layer, never the other way around.

In the Ravioli model, code is grouped in smaller modules. Every module only exposes what needs to be exposed, but every module can still access every other module.

About 10 years ago, it seemed to me that the Ravioli model is better than the Lasagna model. After all, in Java you also have Java modules which can easily call each other (and I had the impression that there was no real structure between all the different Java modules). To me, the Lasagna model seemed the result of non-object-oriented old code, while the Ravioli model seemed more modern, more object-oriented.

Nowadays, I tend to go back to the Lasagna model, but with a Ravioli model built-in. This is:

  • The application is built using different layers, like in the Lasagna model
  • But within the layers, the code is still split up in between different modules that can access each other, like in a Ravioli model.

Certain circular references may be difficult or impossible to remove. An example is the following: Suppose you have a FileWriter class in your application and a Debug class. The Debug class will need the FileWriter class since it needs to write files with debug information. On the other hand, the FileWriter class may also want to use the Debug class.

Notice that the circular reference in this example may already lead to problems (the FileWriter class could call the Debug class while writing a line, but the Debug class uses the FileWriter class to write the debug information, result: stack overflow).

In this case, the problem could be easily solve by not using the FileWriter class in the Debug class, but to use the native iostreams (if you're developing in C++). In other cases, the problem might be much harder to solve.


Good software is designed in layers with clearly demarcated boundaries between them. ie: If you have a layer, you need to be able to clearly articulate what it does, why it's there, and what it depends upon. Circularities make this difficult to achieve, and should generally be removed. (Microsoft has spent lots of effort in Windows 7 to improve the layering of Windows, by removing Circularities.)

Just trying to confirm my suspicion that this application is designed horribly.

This would definitely support that theory, but IMO, you'd need more than just a few circular references to draw that conclusion.

To answer your original question: yes, circular references can be helpful at times. Mutually recursive functions are a good example of that kind of thing. However... that's a circular reference safely hidden away within a module. For inter-module dependancies, a circular dependency usually means that your code isn't correctly split out across the modules, and this can require some significant refactoring to fix. (Including adding new sorts of abstactions to bridge the gap, etc.)


Concretely I'd advise using NDepend to detect and avoid dependency cycles.

Are circular references ever necessary?

Excerpt from the article (I wrote): Control component dependencies to gain clean architecture

Dependency cycles between components lead to what is commonly called spaghetti code or tangled code. If component A depends on B that depends on C that depends on A, the component A can’t be developed and tested independently of B and C. A, B and C form an indivisible unit, a kind of super-component. This super-component has a higher cost than the sum of the cost over A, B and C because of the diseconomy of scale phenomenon (well documented in Software Estimation: Demystifying the Black Art by Steve McConnell). Basically, this holds that the cost of developing an indivisible piece of code increases exponentially.

This suggests that developing and maintaining 1,000 LOC (Lines Of Code) will likely cost three or four times more than developing and maintaining 500 LOC, unless it can be split in two independent lumps of 500 LOC each. Hence the comparison with spaghetti that describes tangled code that can’t be maintained. In order to rationalize architecture, one must ensure that there are no dependency cycles between components, but also check that the size of each component is acceptable (500 to 1000 LOC).


Circular project references are a sign of bad design and should be removed if at all possible.

The only justification I can think of for keeping a circular reference would be a back compat issue. Even then it seems like it could be fixed with the use of type forwarders and another assembly

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜