The Two Functions That Did The Same Thing Differently

by Serguey Shinder

The report arrived as a customer complaint, which is the worst way for these things to arrive. Two screens in the same product were showing two different totals for the same account, eleven pence apart, and had apparently been doing so for some time.

Eleven pence is a funny amount. Too small to be a logic error, too consistent to be a fluke. It took most of a day to find, and what I found was two functions, in two different parts of the system, both calculating the same fee, both written from the same original.

They had been one function once. At some point, years before I arrived, the reporting job had needed it and importing it across that boundary had been awkward, so someone had copied it. That was a reasonable decision on the day it was made. Nobody was wrong. The copy worked.

Then, about eighteen months later, somebody fixed a rounding bug. They fixed it in the copy they were looking at, which was the right one for the thing they were doing, and they had no way of knowing the other one existed. There was no comment, no reference, nothing linking the two. The fix was correct and the system became inconsistent in the same commit.

What has stayed with me is how invisible the failure was. Both functions were fine in isolation. Both had tests, and both passed, because each test asserted against the behaviour of its own copy. There was no single place where the contradiction was written down, so there was no place where anyone could notice it.

I used to think about duplication in terms of volume. Two hundred repeated lines felt worse than twenty. I now think the number of lines barely matters. What matters is whether the copies encode a decision that can change. A duplicated string formatter is close to harmless. A duplicated rule about money, or about who is allowed to see what, is a promise made twice, and promises made twice drift.

The fix, when we got to it, was less interesting than the detection. We collapsed them, which took an afternoon. But before that, while we were still arguing about whether to collapse them, we did something I have repeated since. We wrote a test that called both implementations with the same inputs and asserted they agreed.

That test found two more pairs over the following year. In one case we could not merge the copies for reasons that were genuinely architectural, and the test became the permanent link between them, the thing that says out loud that these two places are supposed to mean the same thing.

Duplication is survivable. Duplication that nothing is watching is how a system quietly starts disagreeing with itself.

– Serguey Asael Shinder

Leave a Reply