Cyclic Dependencies occur when two elements or objects depend on one-another.
In Software Engineering we usually consider the case of modules or classes that depend on another module or class, which then also has a dependency back to the original module or class.
Cyclical Dependencies – also known as Circular Dependencies – are generally unwelcome in software, as they lead to tight coupling between the two elements, meaning that the elements cannot be used or modified independently.
Therefore, software engineers will generally try to avoid introducing cyclic dependencies, but sometimes they are introduced inadvertently.
Where cyclical dependencies occur
Cycles can occur between different software elements. Within these, a distinction is made between:
- Types: These form the smallest building blocks of an entity (e.g., classes, interfaces, traits, enumerations).
- Packages: Form the next level up. These contain groupings of types in accordance with the programming language used.
- Physical entities: Usually created at build time. These are deployable, composable, stateless and natively reusable
Dependencies between types
- Use relationship: certain properties or functions of a class can be used by another class.
- Inheritance relationship: subclassification – the properties and methods of a class are passed on to a derived class.
When analysing dependencies, it is important to consider the distinction between use and inheritance relationships.
Inheritance relationships can be more difficult to eliminate because they are usually more deeply embedded in a code
Dependency between packages
Dependencies between packages in software development define the relationships between different software components organised in separate packages or modules.
For example, references to specific objects, or function calls.
Dependencies between physical entities
Analogous to dependencies between packages, dependencies between physical units arise implicitly through type dependencies.
For example, generated libraries derive dependencies of object usage and calls from the original code that can depend on other generated units.
Why cyclical dependencies are a problem
In software development, cyclic dependencies refer to a situation where two or more types or packages depend on each other in such a way that each depends on the existence of the other.
With larger cycles, the reachability within the cycle increases greatly compared to a similar cycle-free structure. This leads to two problem areas:
- Infectability: negative properties (e.g., Error-proneness, high change rates, inflexibility, poor runtime, etc.) or necessary changes to individual elements (e.g., modified method signatures or changed semantics of individual methods) can affect a larger number of other elements.
- Weighting: It becomes more difficult to apply development activities to smaller, selected groups of elements (understanding, tests, changes and builds).
While infectability highlights the negative impact of cycles on problematic individual elements, weighting refers to the impact of cycles on the development processes themselves.
As the system becomes harder to maintain and improve, both its infectivity and weighting increase, leading to higher costs throughout its lifetime.
Cyclical dependencies can also cause developers to lose context and unintentionally create inefficient or even unstable software.
Therefore, in very large software designs, it is even more important to take care to avoid circular dependencies and, where possible, to reduce coupling between modules.
Understanding dependencies with Understand
When Understand analyses a project, it generates a database of entities and dependencies between entities in the source code.
The dependencies are recorded as a reference between the two entities and a dependency based on the dependency type (i.e., call, called-by, include, use-of, etc.).
The generated dependency data offers the following possibilities:
- Quickly browse dependencies for files and Understand architectures.
- List “dependent” and “dependent on” entities for files and architectures
- Export lists of dependency relationships for use in spreadsheets.
- A “dependency browsing” pane displays context sensitive dependency information
- Graphical dependency charts with filters to manage the types of dependencies displayed
When considering a dependency, it is important to know the rules that govern its relationship:
- An entity is any information from your source code, such as a file, class, function or variable.
- A reference is a place in the code that connects two entities. You can view the references of an entity in the Information Browser and check the rules.
- A dependency is a connection based on the reference(s) between two files, classes, architecture nodes and certain other language-specific constructs.
- Entity groups are based on the “parent” property of an entity. The parent part of an entity is determined by the parser during analysis and is language specific. In general, the parent part is based on the “define in” reference type. In the Information Browser, you can see that app is defined in main, so app and main have a parent-child relationship.
As an example, API code should generally not be dependent on user interface (UI) code.
By making use of the graphical dependency charts and the dependency browser, violations can easily be identified.
To ensure that no new violations are added, rules can be added to the Understand CodeCheck dependency checks configuration.
If a change to the API component introduces a dependency on the user interface, a warning will be displayed in the editor.
You can view the dependencies for any file, directory, or architecture by right-clicking on the entity and selecting “View Dependencies” from the context menu.
This will bring up the Dependency Browser, which is docked at the bottom of the user interface by default.
The browser has numerous options with which you can adapt the window to your needs.
Automated dependency rules with Understand
For example, rules to restrict access to secure components, prevent references between specified components or manage the use of 3rd party code that can be continually validated as code is developed.
Ways in which SciTools Understand helps avoid cyclic dependencies
Using Understand can help optimise the maintainability and scalability of your software.
With Understand, you get a better overview of the entire code base.
You can quickly navigate to specific code snippets and solve problems.
In addition, Understand also includes features such as code metrics and charts that help developers monitor the quality and health of their codebase.
They can thus make strategic decisions to improve their codebase and increase productivity.
Using Understand by SciTools is an important step in designing and maintaining software properly from the start.
It helps developers identify dependencies, reduce redundancy, and increase the maintainability and scalability of the project.
You and/or your team are welcome to test Understand for free.