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.
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):
Having a dependency from the bottom layer to the top layer is a circular dependency (cyclic dependency).
Because of the cyclic dependency, there is no layering between components. They are all on the same layer (one giant 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.
Cyclic dependencies are bad. If you find that components are in a cycle with each other, there are three things you can do:
- Repackage them so they are no longer mutually dependent
- Combine them into a single component
- 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.