Why are circular references in Visual Studio a bad

2019-01-14 15:26发布

问题:

Why are circular references in Visual Studio a bad practice?

First, I will describe an example of how this can happen using C# in Visual Studio, since VS will typically inform you if you have a circular reference and prevent it.

First, a Utilities class is created relying only on the code given to you by Visual Studio and .Net. Then, an E-mail class is created that depends on Utilities. Instead of adding both projects to a single solution, a new solution is created and a reference to Utilities.dll is added. Then, some time later, someone decides that they want the Utilities class to send an e-mail and adds a reference to Email.dll. Visual Studio is perfectly happy to let you do this, but now the source won't compile as-is without one of the binaries.

At my place of work it is standard procedure to copy-and-paste binaries when doing development and then only building the projects you're working on. This has led to at least one circular reference in the codebase that has gone unnoticed for over 3 years.

This seems like a very bad practice to me because there is no way to build either project from source without having the DLLs first. That argument falls a little flat to the "practical" people I work with since it seems highly unlikely that we will lose all copies of our binaries simultaneously. The binaries aren't stored in version control at any point, which only makes me more worried.

This seems like a situation that should be avoided, but not a situation that poses any appreciable threat. Are circular references between projects really a big deal or am I blowing it out of proportion?

回答1:

Yes, this is a bad practice for precisely the reason you've stated - you cannot rebuild from the source.



回答2:

This is one of the symptoms of over-modularization.

I worked at a company once with about twenty developers and more than sixty different active projects in the SVN repository. Every project had its own build script and produced a JAR file that was a dependency of at least a half-dozen or so other projects. Managing all those dependencies was so complicated that we wasted a ton of time trying (unsuccessfully, I might add) to set maven projects to automatically fetch all the correct libraries (and the correct versions) for all those little micro-projects.

The funny thing (to me) was that it was really only one project, and it wasn't even something we distributed to the outside world. It was a hosted application, with a web front-end, running on a single server cluster.

Sheesh.

Another side-effect of the architecture was that the same kinds of functionality got duplicated over and over again (not always with the same algorithms, or results, for that matter) in several different projects. I think part of the reason was because people didn't want to introduce a new dependency on an entire sub-project just to get access to a few of its classes. But I think another reason was the people just didn't know what code existed where, and rather than go to the trouble of finding the code they wanted to reuse, they'd just rewrite it in their own project.

Of course, modularity is generally a good thing.

But like all good things, it can be taken to ridiculous extremes.

My advice is to find those circular dependencies and merge the projects into bigger chunks, since the current project breakdown probably represents a false modularity. Better to divide your big project into a few well-separated modules than to have a zillion pseudo-modules creating artificial boundaries between logically-coupled classes.



回答3:

Aside from build issues, circular references always indicate a design flaw. In .NET, a circular relationship makes two assemblies effectively one assembly. If neither one can live on its own without the other, building them separately is just an exercise - it doesn't change the fact that together they represent a monolithic assembly.

I've noticed this a lot with utility assemblies. Must be an anti-pattern.



回答4:

The question I would pose to your coworkers is:

Okay, it is very unlikely we'll lose all our binaries at the same time. But, tell me, what's the benefit of this approach?

If they come with anything different than "We've always done it this way", I'd like to hear it. And, as everybody knows "we've always done it this way" is NOT a good reason and they shouldn't defend it.



回答5:

circular dependencies are bad because:

  • project A references project B
  • when project B changes, project A needs to be rebuilt
  • now if project B also references project A
  • then when one of them changes, the other one needs to be rebuilt
  • which causes the other one to need to be rebuilt
  • which causes the other one to need to be rebuilt
  • etc.

this is potentially an infinite-loop in the build process



回答6:

If you're having trouble convincing your "pragmatic" colleagues that this is a bad practice, offer a pragmatic solution: Remove these circular references and build times will go down since loads of unnecessary rebuilding will be avoided.



回答7:

Hm, I think in terms of the OP question, about building projects from scratch, it is bad practices, due to:

  1. Inability to build from source / scratch - What if I maintain several versions of code. Between each version, a few changes to each of the libs have been made.. The only way I can feasabily maintain these concurrent versions, is to now store the compiled binaries in my version control. This also means comparisons between each library version is that much harder. (I need to find out, what version is being used, then go look at the actual source code for the lib, rather than just a direct diff on the source itself.
  2. Possibly harder to push out patches to customers which have incrementally patched versions.. Where and what to patch?


回答8:

Yes, it's a big deal, because it leads to unmanageable component compositions, and most likely some form of build errors and very likely some deployment errors, at some point in the project life cycle.

Visual Studio 2008 explicitly prevents circular references between projects (not binaries) in solutions. As you have stated, that is not the case for binaries. I believe that is because VS2008 expects that binary references are managed independently and it expects that you are building those projects independently. Generally speaking, that means all binary references should be viewed as third-party components that represent a distinct one-way relationship (between your code and the binary).

Furthermore, MSBuild allows you to use the SLN file to build everything, if all your projects use VB or C#. This allows for the creation of an uber-solution file, which is ideal for an automated build process. The catch is that in order for such a master solution file to work, all of the projects in the SLN must utilize project references. Therefore, in order to take advantage of this, by definition, Microsoft expects that you are not employing circular references, since they are explicitly forbidden by the Visual Studio IDE (for project references).

Scott Hanselman makes reference to an uber-solution file in the following post:
Hack: Parallel MSBuilds from within the Visual Studio IDE
http://www.hanselman.com/blog/CategoryView.aspx?category=MSBuild