July 2017

Why Cyclic Dependencies are Bad

In software development, divide and conquer is a design strategy where you recursively break down a problem into two or more sub-problems, until the problem becomes simple enough to be solved directly. This is where software components (packages, assemblies, modules, classes, etc.) come into play. Components break up large blocks of code into smaller, more manageable pieces. One rule of thumb is a component should only include closely related code. This makes deployment and maintenance easier to manage. Ideally, you would like all components to be independent of each other, but inevitably some dependencies are necessary.

Levelization

Often we quantify code as “high-level” or “low-level”. This is a standard way of managing dependencies (levels). You have high-level layers and low-level layers. Each layer should depend only on the layers below it, not on any layer above it. If you are new to a codebase, it is often helpful to understand what the high-level code is and what the low-level code is.

This is helpful because you can make sure that low-level code does not inadvertently rely on high-level code. You can easily order your components using Lattix Architect by applying component partitioning to the DSM. This levelization is important because that is how you divide and conquer. First you divide the software into components, then you conquer by making sure there are no dependencies between components. In a typical embedded software system, there is usually a high-level communications layer, a middle hardware abstraction layer (HAL), and a low-level drivers layer. Here is a standard picture (standard layer):

Standard Software Architecture

Having a dependency from the bottom layer to the top layer is a circular dependency (cyclic dependency).

Cyclic Dependency

Because of the cyclic dependency, there is no layering between components. They are all on the same layer (one giant component).

Giant Software Component

This has ruined the “divide and conquer” approach of having components. Instead of having three components, now you have one giant component that is three times larger and much more complicated and can not be developed or tested independently. (With Lattix Architect, it is easy to see cyclic dependencies in the DSM after it has been partitioned).

Why Cyclic Dependencies are Bad

Cyclic dependencies between components inhibit understanding, testing, and reuse (you need to understand both components to use either). This makes the system less maintainable because understanding the code is harder. Lack of understanding makes changes harder and more error-prone. Also, if components are in a circular dependency they are more difficult to test because they can not be tested separately. Cyclic dependencies can cause unwanted side effects in a software system. When you make a small change to a software system it can cause a ripple effect to other modules, which can have global ramifications (bugs, crashes, etc.). Finally, if two modules are tightly coupled and mutually dependent on each other, reuse of an individual module becomes extremely difficult or even impossible.

Summary

Cyclic dependencies are bad. If you find that components are in a cycle with each other, there are three things you can do:

  1. Repackage them so they are no longer mutually dependent
  2. Combine them into a single component
  3. Think of them as if there were a single component

The best solution is to detect and correct cyclic dependencies as soon as they occur. You can do this by checking your architecture regularly. A tool like Lattix Architect can help.

Motivation for Software Architecture Refactoring

Refactoring is commonly applied to code, but refactoring can also be applied to other development artifacts like databases, UML models, and software architecture. Refactoring software architecture is particularly relevant because during development the architecture is constantly changing (sometimes for the worse; see our blog post on Architectural Erosion) and expanding. Software architecture refactoring should happen regularly during the development cycle.

We have talked in the past on how to perform architectural refactoring (see our blog post What is Architectural Refactoring?). In this blog post we talk about why you should.

Why refactor software architecture?

The ongoing success of a project is based in large part on the software architecture. Software architecture directly influences system qualities like modifiability, performance, security, availability, and reliability. If the architecture is poor, no amount of tuning or implementation tricks after the initial development will help significantly improve system qualities. You need to evaluate and refactor your software architecture early to know whether it will meet your requirements.

Software architecture evaluation and refactoring should be a standard activity in any development process because it is a way to reduce risk, it is relatively inexpensive, and it pays for itself in the reduction of costly errors or schedule delays. Architecture also influences things like schedules and budgets, performance goals, team structure, documentation, and testing and maintenance. As software teams grow and/or become more distributed, understanding software architecture becomes even more vital. If everyone on the team does not have a clear understanding of the architecture (what components depend on other components, etc.), defects start to develop in the code.

For example, if you were building a house, you would carefully examine and follow the blueprints before and during construction and make changes to them as new requirements are introduced. In construction, this extra time is worth it because it’s better and cheaper to find out the homeowner wanted an extra bathroom during design or construction than on moving day! The same is true for software development.

What is software architecture?

To properly refactor a software architecture, you need to understand what information is relevant. Software architecture is the structure of a system. It is made up of software components, their properties, and their dependencies. The architecture defines the modules, objects, processes, subsystems, and relationships (calls, uses, instantiations, depends on, etc.). Architecture defines what’s in a system and provides all the information you need to know how the system will meet its requirements. Software architecture fills the gap between requirements and design. To refactor the architecture, it has to be understandable and easily visible using Dependency Structure Matrix (DSM) and Conceptual Architecture Diagram (CAD) views.


software architecture

Finally, the architecture creates the requirements for the low-level designs.

When should you refactor software architecture?

In a typical project, you design and think about the architecture only at the beginning. But, as stated earlier, architecture refactoring can and should be applied at all stages of software development. As an example, in agile development architecture evaluation and refactoring should happen once per sprint.

Architecture refactoring is particularly helpful after implementation has been completed. This might happen when an organization inherits a legacy system or if you are put in charge of an existing application. Understanding and refactoring the architecture of a legacy system is useful because it gives you a complete view of the system and answers the question of whether the system can meet the requirements in terms of performance, security, quality, and maintainability.

What are the goals of software architecture refactoring?

“If you don’t know where you are going - any road will get you there” - Cheshire Cat

If you don’t know what your goals are or if the goals are too vague (“the system shall be highly modifiable,” “the system shall be secure from unauthorized break-in,” “the system shall exhibit acceptable performance”), then there can be a misunderstanding of what needs to be done during refactoring or when refactoring has been completed. The point is that system attributes are not absolute quantities, but exist in the context of specific goals.

Not all system attributes can be improved with software architecture refactoring. Usability is a good example. This has more to do with the user interface than the underlying architecture. Although if the user interface has its own module with limited dependencies it can be easily swapped out for a different interface (i.e. using the web instead of desktop GUI).

System attributes that are determined by the architecture and can be improved with refactoring include:

  • Performance - This is how responsive the system is in certain workload conditions (as specified by the end users) or how many events it can process during a certain period of time.
  • Reliability or availability - This is the ability to keep the system up and running over time. The system needs to recover gracefully from failures or unexpected behavior.
  • Security - This is the ability to resist or defeat unauthorized usage and/or denial of service attacks while still providing the correct service to legitimate users.
  • Modifiability - This is how quickly new features and updates can be made to the system based on changing requirements or bugs found in the field or through testing.

Summary

The benefit of software architecture refactoring is uncovering problems earlier in the development cycle when they are cheaper and easier to fix. It produces a better architecture that helps with future development and maintainability. An iterative and consistent architectural refactoring process increases everyone’s confidence in the architecture and in the system as a whole.

Software architecture refactoring gives everyone a better understanding of the architecture. This can then easily be communicated to all interested parties including product management, other developers, QA, etc. Lattix Architect is a great companion for architectural refactoring and evaluation as it makes the visualization of the architecture easier and allows for quick what-if analysis of the architecture.