In the previous chapter we’ve first seen the Change Stream, the constant Stream of Changes an Engineer makes to an application. We’ve visualized splits in the Change Stream as tunnels, and we’ve split it according to properties of the Changes in the Stream: Intentions, their frequencies and their relative frequencies.
We’ve ended up with the idea that mismatches between the Change Stream and our application’s design has the potential to create Inefficiencies. In this chapter, we’re going to further explore this correlation between the two. As the Change Stream is supposedly beyond our control, we are only left with designing our applications to fit it better.
Correlation to Change
Let’s start with a very basic thought. Let’s presume that our application has only two Modules A and B, and does only A and B. With it, absolutely no one has any Intention or reason to make a Change to both together. That would be an empty tunnel within the Change Stream.
If no Change comes through that empty tunnel, there is no benefit in having an intersection between the two Modules. The intersection only creates the potential for unintentional consequences, for Changes coming through the other tunnels. When it comes through, a Change with the Intention of A alone now has the option to entail an Instability. Same for B.
The odds of it happening has something to do with the size of the intersection. The smaller it is, the less Instabilities would occur. A size of zero would be best, because there is no Intention to Change A&B together. The current intersection is more than redundant, as it carries the potential to cause harm. Let’s examine the consequences of its removal.
As there is no intersection between the two, no matter how frequently the Changes to A will enter the stream and no matter how frequently Changes to B will enter the stream – one can not entail an Instability to the other. They are decoupled and independent. But, what if the unexpected has happened and eventually a single needed Change to A&B has entered the stream?
Obviously we’d now need to make two independent Changes. One for A and one for B. Should we now regret that we removed the intersection between the two? The answer is a definite no. Our past removal of it has prevented many Instabilities and avoided business loss. Until this very moment, it was beneficial. The real question is, will it keep being beneficial as is.
We’ve decided to put in a new intersection, to refactor our application in a non-wasteful way. A year has passed and no Change for A&B together has entered the Change Stream. Meanwhile, Instabilities started to occur, because the refactor resulted with a non-zero odds of these happening. That is a non-beneficial result.
The Unsteady Stream
Alternatively, we’ve decided not to refactor and remain without an intersection. We’ve done two Changes instead of one. Alas, a month afterwards another such Change to A&B has entered the stream. And again we’ve decided not to refactor. A month afterwards, another one. And another one.
By not refactoring, Inefficiencies started to occur. We had not intentionally created those, they were created out of the mismatch between the Change Stream and our application’s design. And it was the Stream’s internal distribution that has Changed first. It wasn’t our application’s fault. We’ve done nothing wrong.
The Change Stream still has the very same three tunnels as before, it’s only the frequencies that have Changed. The Stream’s internal distribution ended up being almost equal and uniformed. And no one says it will never Change again. It may be continuously Changing. The Change Stream itself is unsteady.
One day the A&B Changes will cease for a year, and some unknown time later they’ll start happening again on a weekly basis, for another month. We are limited in predicting this unsteadiness. We can not predict when it will occur, when it will become stable again and for how long. We can not predict how much effort another future refactor will take, and can not compare it against a series of future unknown Changes that we can also not predict their effort. Even if we did, we would only be wasting too much time on refactoring.
We can measure the Changes in the Stream’s distribution. There is entropy (which is somewhat similar to Gini Index / Gini Coefficient) that inspect the uniformity of a distribution. There is even weighted entropy that could tell what it Changes to. If you happened to click on any of the links here, you’d see it’s a lot of math. I guarantee you, it’s way easier to calculate than we think, but it’s not beneficial enough. Math and statistics could help us monitor and trace the Stream Change, but not predict what its future is going to be like.
There is also a lot in the practice of Software Engineering to overcome the Stream’s unsteadiness. Just pulling a few out of my hat:
- Have physical folders that mirror the Modules. Folders named A,B. Although A&B might be a weird folder, at the very least we’d be able to foresee the potential unintentional outcomes during Code Review. But it doesn’t scale. A folder for A&B, and A&B&C, and A&B&D and so on… I think that was once called “Common” and it was a mess to manage. It would end up being a huge intersection.
- Reverse dependencies
- Gradual deployment of Changes to intersections.
- External packages
- Multiple applications (to be covered in the future series Breaking Change)
- And many more
All of the above might postpone refactoring, or reduce the effort needed to refactor. Which is good on its own.
Making sure our application is ready for future Changes, is something Shai Yellin calls optimizing for Changeability, which is somewhat doing less of the above and doing something else entirely. I 100% agree with him, which is why we are going to do something else entirely. We are going to dig even deeper into the Change Stream. We’ll see if we can make our application more agnostic to the Stream itself, to overcome its unsteadiness. Starting with the next chapter, where we will talk about our miserable employees who suffer from tunnel vision.