When a programmer sees duplicated code, our gut reaction is to recoil in disgust. After all, the Once and Only Once principle is at the very core of the programmer’s thought process, but I would argue that this caveat is even more fundamental to the concept:
“Beware of introducing unnecessary coupling when refactoring forĀ Once and Only Once.“
What does “unnecessary coupling” mean? That’s what I want to look at here.
When you apply the Once and Only Once principle, you’re defining everything that uses that code to work the same way. For a simple example, consider the code to reverse a string (in place):
int n = str.length();
for (int i = 0; i < n / 2; i++)
swap(str[i], str[n - i - 1]);
If I advocated for writing out that code every time you had to reverse a string, you’d rightly conclude I was being ridiculous.
Fundamental Sameness
The code to reverse a string clearly belongs in a function because reversing a string is something that’s Fundamentally the Same no matter what string we’re trying to reverse anywhere in our software. If we discover a better way to do it, we want to be able to change it in one place.
Strings are also fundamentally the same. We can define them in strict terms. They don’t change, and even if the programming language designers decided to change the implementation of a string, all strings in our program have to change at the same time.
As the programmer we have the power to shape the internals of the program however we want, and this is powerful. We can define how strings are stored, and build upon that by defining how to reverse them.
However, we rarely get to define the real world that we’re interacting with.
Incidental Sameness
Anyone who has ever created business logic in their software will understand the pain of making rigidly defined software that represents the… less rigidly defined.. real life business rules of a company.
Let’s say your company has account managers separated into two groups. One group of account managers handles industrial clients and the other group handles commercial clients. The head of the industrial account manager group comes to you and says they need a new feature in the CRM software: their new group policy says we need to review accounts monthly, and they need a reminder alert generated if any account hasn’t been reviewed in 28 days. You go and check with the head of the commercial accounts manager group and they agree that this is a good idea.
You write a function to tell you when to generate alerts for an account:
bool generateAlert(Account acct) {
return Today.Subtract(acct.LastReviewedDate).TotalDays >= 28;
}
Do you see the problem? The two groups may both have the same policy: “accounts must be reviewed monthly”. However, those policies are only Incidentally the Same. There’s only one function and it defines alerts on all accounts to be generated the same way regardless of which group handles the account. If the head of the commercial group later comes to you and says they want an alert if it’s been 14 days since the account has been reviewed, then you’ll need to rewrite this feature.
Yes, it’s a contrived example and changing it is unlikely to be a big deal, but what you’ve done is created unnecessary coupling between the two groups. You can’t change the code for one group without affecting the other. In fact, tomorrow, the commercial group may not want alerts at all, or they may want alerts generated in different formats or sent to different people. Depending on the structure of your company, the two groups may need to operate completely differently due to differing client needs. It’s unlikely that management would have created two different groups unless they recognized the need to operate independently.
Application to Industrial Automation
As Engineers we love to copy tried and true designs. That gripper design works well, so let’s use the same gripper on robot 1 and robot 3. Those VFD drives seem to be really robust, so I think we should use them on all the pumps and conveyor drives of this new machine.
However, industrial machines are constantly modified for a variety of reasons: changing product and process needs, temporarily bypassing failed components, or replacing components with different parts because a better product is available, or the old model is no longer being manufactured.
When programming an industrial machine, you need to treat identical components as only Incidentally the Same. Your program needs to allow for the complexities of real life in a manufacturing environment. Components that are only incidentally the same will often need to be changed, and they usually won’t all change at the same time. Even if you want to change all the pumps on your machine, you’ll likely only change one this week, make sure it works, and then change the other three during some scheduled downtime next month.
That doesn’t mean you shouldn’t follow the Once and Only Once principle. Make a function block (or an Add-on Instruction in Allen-Bradley) for communicating with your specific model of VFD drive and use it everywhere you use that drive. That’s because VFD drives with the same part # are Fundamentally the Same. Make a function block for logging events to your plant-wide data collection system, because you’ve created an explicit definition of an event, and you can control it. Write common functions for calculating the distance between two points, or calculating voltage given current and resistance, because those are defined and won’t change (or, if they do change, you’ll want to change them everywhere).
On the other hand, the chance that the grippers on robot 1 and robot 3 are going to be identical two years from now is vanishingly small. And those pumps? One of them will have a bypassed flow sensor for a week next July. Plus, someone’s got a crazy idea for controlling the second conveyor section based on the measurement from a laser thickness gauge.
Be thoughtful about code re-use. Use it for things that are Fundamentally the Same, but beware of components that are only Incidentally the Same. Don’t create unnecessary coupling between incidentally similar components.