Temporal Coupling

Temporal Coupling means that two (or more) modules change together over time. Exploring Temporal Coupling in our codebases often gives us deep and unexpected insights into how well our designs stand the test of time.

Understand Temporal Coupling

CodeScene provides several different metrics for temporal coupling. The tool considers two modules coupled in time:

  • if they are modified in the same commit, or
  • if they are modified by the same programmer within a specific period of time, or
  • if they refer to the same Ticket ID in their commit messages.

The temporal coupling graph in CodeScene shows a hierarchical view of your temporal coupling. Hover over a label in the graph to highlight its dependants as illustrated in Fig. 60.

The temporal Coupling hierarchical view

Fig. 60 Hover over a file in the temporal coupling graph to see its dependants.

The initial graph is great to spot interesting temporal dependencies (we’ll discuss them soon). CodeScene also presents a tabular view of the temporal coupling in your system, as illustrated in Fig. 61.

Temporal Coupling table.

Fig. 61 The temporal coupling table gives you all the details.

Coupled Entities
Two files that tend to change together over time.
Degree of Coupling
How often the files change together. The first pair in Fig. 61 change together 74% of the time.
Average Revisions
This measure is used to filter out temporal couples that don’t pass a configurable threshold. We do not want to consider two files coupled just because they were created in the same commit.

In this guide you’ll see just how powerful Temporal Coupling is. The more experience we get with the analysis, the more use cases there seem to be. For example, you’ll learn to use the Temporal Coupling results to:

  • Detect software clones (aka copy-paste code).
  • Evaluate the relevance your unit tests.
  • Detect architectural decay.
  • Find hidden dependencies in your codebase.

Explore Your Physical Couples

Why do two source code files change together over time? Well, the most common reason is that they have a dependency between them; one is the client of the other. Fig. 62 shows an example of such a case.

Temporal sample on unit test

Fig. 62 Temporal sample on unit test.

As you see in the picture above, a unit test tends to change together with the code under test. This is expected. In fact, we’d be surprised if the temporal coupling was absent - that would be a warning sign since it indicates that your tests aren’t being kept up to date or aren’t relevant.

A physical dependency like this is something you can detect from the code alone. But remember that Temporal Coupling isn’t measured from code; Temporal Coupling is measured from the evolution of the code. That means you’ll sometimes make unexpected findings.

Look for the Unexpected

Always look for unexpected temporal couples. As soon as you find a logical dependency that you cannot explain, make sure to investigate it. Fig. 63 shows an example.

Unexpected temporal coupling

Fig. 63 Unexpected temporal coupling.

The table in Fig. 63 shows a strong temporal coupling between a LinkTagHelper.cs and a ScriptTagHelper.cs. You also see that their unit tests tend to be changed together.

While those two classes seem to solve related aspects of the same problem, there’s no good reason why a change to one of them should imply that the other one has to be changed as well.

When you find an unexpected change pattern like this you need to dig into the code and understand why. This is where CodeScene’s X-Ray feature proves invaluable (see X-Ray).

As you X-Ray a temporal coupling cluster you’ll often find that there’s some duplication of both code and knowledge. Extracting that common knowledge into a module of its own breaks the temporal coupling and makes your code a bit easier to maintain. You see, temporal coupling often suggests refactoring candidates.

Investigate Temporal Dependencies across Architectural Boundaries

Temporal Coupling is like bad weather - it gets worse with the distance you have to travel. In our code, it’s a big difference if we need to modify two files located in the same package versus modifying files in different parts of the system. That’s why you want to look for temporal dependencies that cross architectural boundaries.

On a side note, some architectures will lead you to exactly those expensive change patterns. The most notable one is a layered architecture. You will often find that most new features implies modifying the majority of your layers. Temporal Coupling helps you keep track of it and assess the situation.

Detect Change Patterns Across Repositories

CodeScene’s temporal coupling filters can be used to make it easier to detect changes that ripple across repository boundaries. However, if you have tens or hundreds of repositories it’s going to be painful to configure. To solve that CodeScene provides a special view that only focuses on the temporal couplings that cross repository boundaries, as show in Fig. 64.

Temporal coupling between repositories

Fig. 64 Detect temporal coupling between repositories.

This view provides detailed information on exactly what files in different repositories that have implicit dependencies between them. Again, look for surprising patterns that violate your expectations or architectural principles.

Once you’ve identified such change patterns you use X-Ray to resolve the coupling on a function level. Since X-Ray works across repository boundaries, you’re usually able to uncover surprising patterns that aren’t visible in the code nor the Git repository (see X-Ray for more details).

Use Temporal Coupling to predict Omissions

So far you probably got the impression that Temporal Coupling is something to avoid. And you’re right. At least in the majority of all cases. But there are some situations where you actually wants Temporal Coupling:

  • You want your unit tests to evolve with the code under test.
  • You want your documentation to be updated together with the system it describes.
  • You have parallel implementations for different platforms.

The final point is particular interesting since it shows one of the main strengths of Temporal Coupling: you can identify change patterns across different languages and techniques. Fig. 65 shows an example from Roslyn, Microsoft’s open source compiler platform.

Temporal coupling between languages

Fig. 65 Temporal coupling between languages.

If you have expected Temporal Coupling like this then use it to your advantage. Use the knowledge of your existing development patterns to guide your code reading, commits and to plan your modifications.

Dig Deeper with Sum of Coupling

Sometimes, it may be hard to prioritize if you have a lot of temporal couples in your codebase. In that case, use the Sum of Coupling results to guide and prioritize amongst your temporal couples.

Sum of Coupling is a measure of how often a specific file in your codebase is changed together with another file (any other file). The idea is that files that often changes together with others are significant from an architectural perspective.

Change the Temporal Coupling Thresholds depending on your Codebase

In order to avoid biases like large re-organizations of the codebase, CodeScene lets you configure a threshold value for the maximum changeset to consider. This is something you specify in the analysis configuration for your project as illustrated in Fig. 66.

Temporal coupling configuration

Fig. 66 Temporal coupling configuration.

The temporal coupling thresholds are configured on two different levels that correlate to the resulting analysis views:

  1. By Commits: This configuration refers to files that tend to be changed as part of the same commit. This is the strongest level of coupling, and the primary inspection point.
  2. By Logical Changesets: This configuration refers to files and logical components that might be changed in _separate_ commits, maybe even located in different repositories. CodeScene lets you specify the threshold separately as you typically want to use lower thresholds when identifying patterns across commits in different repositories as it’s an aggregated metric.

As a specific example configuration, the settings in Fig. 66 for the By Commits section means that the temporal coupling algorithm will respect the following thresholds:

  1. Ignore all files with less than 10 commits since the coupling trend isn’t strong enough yet.
  2. Ignores all changesets/commits where more that 50 files were changed together since we want to limit potential false positives.

You’ll find that the default values are typically good enough for the initial analyses. You typically lower the thresholds in case you don’t find any temporal coupling.

Finally, please note that CodeScene lets you specify the thresholds for “Temporal Coupling By Commits” separate from “Temporal Coupling Across Commits”. The rationale is because you typically want to use lower thresholds when identifying patterns across commits in different repositories.

Complement Your Intuition

If you’re an experienced developer that has contributed a lot of code to a particular project then you probably have a good feeling for where the most significant Hotspots will show-up. You may still get surprised when you run an analysis, but in general most analysis findings will match your intuitive guess. Temporal Coupling is different. We developers seem to completely lack all kind of intuitive sense when it comes to Temporal Coupling.

A Temporal Coupling analysis often gives us deep and unexpected insights into how well our designs stand the test of time.