Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Circular dependencies between classes: why they are bad and how to get rid of them? [closed]

I feel circular dependencies (aka circular references) mean bad design and harm the project. How can I convince my teammates and my manager?

My project is a dependency mess. Is there a methodology to get rid of wrong dependencies and then maintain the clearness?

like image 328
polina-c Avatar asked Dec 07 '22 18:12

polina-c


1 Answers

Why are circular dependencies (CiDs) bad?

Two reasons:

  1. Maintainability.

    You want your code to be layered, i.e. you want to have a top-down diagram of dependencies (a diagram showing all arrows going down, and no arrows going up). If you have CiDs, your code is not layered.

    Why does the layered code mean maintainability? Because, every time you change the interface of a class, you can be sure that nothing below it will be affected. This knowledge makes maintenance and development of the system cheaper and less error-prone.

    The good news is that with modern dev tools like Visual Studio, Eclipse, NetBeans, and IntelliJ, it is fairly easy to generate a diagram for your project. And, there is a simple trick if there is no tool yet.

  2. Reliability.

    You do not want unexpected infinite recursion in production. For example, when your configuration wants to log an error and your error-logger wants to read the name of the log file from configuration (your tests will pass, because there is no error in the test environment). (Unfortunately, unexpected recursion can be developed even without CiDs.)

Are there good CiDs?

Some CiDs are valid, helpful, and do not affect maintainability or reliability: String and Object, File and Folder, Node and Edge. Usually, such circles sit within one package and do not contribute into circular dependencies between packages.

How do I detect package CiDs?

You can detect CiDs visually on the diagram of dependencies (see links to generation tools above).

If your project is huge or you want to watch for CiDs continuously, it is simple to implement a tool that uses reflection to detect CiDs (traverse classes or packages depth-first and stop at the first back reference).

Note, that if one package is declared inside other, this does not mean they depend on each other, unless classes from them reference each other.

My project is a dependency mess. Is there a methodology to fix it?

dependency mess Yes, there is. Here are the steps:

  1. Design the desired structure.

    a. Create a plain list of existing packages, like this: enter image description here

    If your package structure is hierarchical, flatten it, and do not forget the root package.

    b. Organize the packages.
    Re-order the list so that packages with a lower abstraction level are closer to the bottom and packages with a higher abstraction level are closer to the top. If a package contains classes of both low and high abstraction levels, you may want to break this package into two.

    If you have too many packages, first break them into layers, order the layers, then order the packages inside those layers. This step should result in your desired order of packages, where you want the dependencies to go down and not up.

    c. Organize classes in the packages the same way.

  2. Make the process measurable.

    Measure the distance to the desired structure so that you can see the progress as you get closer to the goal. For each class, count the number of wrong dependencies (dependencies directed up). The sum of these numbers will become your metric. At the end, it will be zero.
    Set up monitoring to detect new wrong dependencies. You want to stop your teammates (and yourself) before they are about to check in another wrong dependency.

  3. Resolve wrong dependencies one by one. Usually it is easier to go from bottom to top, as you’ll want to clarify the basics first.

    These tips may help:

    A. You have a "death star” or “god object,” i.e. an object that is known by classes it knows about. In other words, such a class is a part of many circular dependencies which may result in the dependency of each class on almost every other class.

    Solution:
    Most death stars can be resolved by splitting them into two (or more) classes, where one class contains just state and very basic operations and another class contains advanced operations (usually the second class is static). enter image description here

    B. You do not have a circle between classes, just between packages.

    Solution:
    Consider moving some classes to other packages.

    C. You have a class that uses some methods of upper class, but does not own its instantiation.

    Solution:
    Use a callback interface or callback method (pattern Observer). enter image description here

    D. You have two classes that create each other and use each other’s methods.

    Solutions:

    • Combine the classes into one class.
    • Put the classes into one package and declare their relationship as a good CiD.
    • Create a Factory class and interface for one of the classes.

enter image description here

How can I maintain clarity in my project?

If you have a small team and a small project, simply explain the rules to your teammates and occasionally check the diagram.

If the project is large and complicated, you should establish a process to receive an alert or rejection every time someone compiles or checks in a wrong dependency.

like image 137
polina-c Avatar answered Feb 19 '23 10:02

polina-c