The file dependency analysis finds circular file dependencies that can have a negative effect on reusability. You can find this analysis at two locations:
A file depends on another file if it requires the latter to compile or run.
File dependencies form a dependency graph. Files are nodes in the graph, while "depends on" is an edge (arrow) from the user file to the required file. If it is possible to follow the arrows from a file via required files back to the original file itself, it's is circular dependency. The files along the path are part of a circular group of files. In a circular group, it's possible to traverse from any file to another file along the "depends on" edges.
The smallest possible circular group consists of two files that refer to each other. In a larger circular group, several files may form a circle in the graph.
In practice, it is possible that all or almost all of the files in a project form a giant circular group. This may be due to a lot of files depending on a central file, which may contain the main form of the project, or an important class or module. For example, in the Visdata project that comes with Visual Basic 6.0, there are 36 source files, out of which 34 belong to a single circular group.
In the example on the left, files A, B, C and D form a circular group. It is not possible to use any of these files without using the entire group. File E is not part of the group. It can be used independently of the others.
CDI metric example
To achieve high reusability, file dependencies should be kept to a low level. However, all file dependencies are not equally bad. After all, a file dependency might indicate code reuse - you are using the same code in several locations. That's why it does not make sense to get rid of all dependencies (which would be done by putting all the code in a single monolithic file). You only need to get rid of the bad dependencies — the ones leading to circles. Circular dependencies are the pathological dependency structure that should be avoided.
When the need for code reuse arises, it is possible to put an existing file or set of files in a new project. Should you need to use a file in a circular group, you will need to either include the whole circular group in your project or rewrite the code. It's very likely that the circular group contains code that is not necessary for the task at hand. Adding such a block to another project is likely to introduce a lot of dead code in the new project.
Low circular dependency promotes code sharing and may prevent the need for copy & paste coding. Copy & paste coding is not good since it duplicates existing functionality. Later it becomes difficult to maintain the code that exists in several locations.
Circular dependency index (CDI) is a metric that evaluates the degree of circular dependencies in a system. This metric has been developed by Aivosto. The higher CDI, the worse the system design.Circular dependency index CDI = (FC - C) / (F - 1) when F > 1 CDI = 0 otherwise FC = source files in circular groups C = number of circular groups F = number of source files
The target value for CDI is 0%. The maximum value is 100%, which represents the worst case where all files form a giant circular group.
You can lower CDI by either removing files from circular groups, or splitting large circular groups into smaller circular groups. In practice, it is easier to write new code with a low CDI in mind rather than rewrite existing code.
Write modular code. Arrange procedures and data elements in logical groups. Minimize the number of calls between files.
Place data definitions, such as constants, enumerations and user-defined types, in a dedicated data definition module. A self-sufficient data definition module - one that does not require definitions made in other modules - has no dependencies to other files. This way you can use the definitions anywhere without creating a circular dependency.
Use object-oriented techniques to limit two-way dependencies. Call methods of an object to send messages and data to the object. To provide messages and data from the object, raise an event. Handling of an event does not create a file dependency back from the class to its users. Thus, events prevent circular dependencies, at least what constitutes compile-time dependencies. A well-designed class should not depend on how its events are handled or if they are even left unhandled. When the class is reused, the event handlers may be rewritten or the events might even be left unhandled.
Breaking an existing circularity is easiest done where the number of dependencies out of a file is limited, perhaps just one or two. A high number of dependencies either in or out of a file indicates a central file. Breaking circularity by modifying the central file may turn out to be difficult.
Sometimes it is not possible to avoid that files depend on each other. In this case, try to keep the dependency effects to a minimum. Two mutually dependent files are not a worst case scenario. After all, they are just two files that require each other and the effect of circular dependency is limited.
Breaking existing circularities is a trade-off between modularity, reusability, simplicity, performance and other design goals. It may make sense to leave a certain degree of circularity instead of using other more complicated or even unsuitable designs.
How do you detect the references that cause the circular dependencies?
©Aivosto Oy - Project Analyzer Help Contents