back
10 comments
To me, the real issue is that a design decision should ideally be expressed at only one place in the code. Then, if that decision is changed, you change that one place in the code. If the design decision is reflected in two or more places, chances are that when changes are made, one of those places will be missed. Some languages force you to write things twice (the prototype and the definition in C and C++), but at least the compiler helps find mismatches.
More than just code duplication, what about functionality duplication? By functionality I mean code that does the same thing but is written significantly differently.

This is by far the most insidious problem as you can't just wrap duplicated code up into a function and move all the code to one place.

Usually this problem appears not just as functionality duplicated in many places but each time the functionality is also intertwined with other features and cannot be decomposed to get rid of the duplication.

In most styles of programming there is no way to get around this issue as it has to do with program "design." Unless you can make a design that anticipates the duplication, likely a duplication is inevitable.

However, if you make your variables immutable as much as possible you can get around this issue and solve what was seemingly a "design" problem but was in fact only a problem with mutable variables. It's too bad the majority of programmers don't know about this.

This is how Lisp gets you to think about code. Minimize side effects (essentially the same argument as immutable variables), and if you're likely to use something more than once, turn it into its own function. Then stick it into your private library if you're at all likely to see it again in the future.

Something that doesn't occur often enough, but should: then rewrite the function for various optimizations - speed, size, security, whatever, while ensuring the same input will always produce the same output.

I'm from the haskell universe.

It's more than just side effects. A common design issue people hit is that midway through a projects lifetime they hit a feature request that is basically a slight permutation of an existing feature.

They realize that they cannot reuse the code because of the way they "designed" the program. Ideally you want to reuse existing code, or if no function specifically supports this feature you can encapsulate a relevant line of code into some sort of container so that it can be reused in multiple places. This is a way of decomposing your code, or aka: dividing a function into multiple pieces so components can be deployed in multitudes of other location thereby maximizing code re-use.

What happens is that most people realize that no matter how much they plan out a design they cannot execute the decomposition procedure above. A design choice made at the inception of the project fixes the shape of the code and limits flexibility. Thus they do code duplication as a workaround hack.

I am saying that this problem is actually caused by mutability.

What is seemingly a structural problem only occurs because you have a mutable variable. You can cut your code and divide a primitive into a thousand possible pieces if you wanted too but the minute you have a mutable variable in your code the division cannot be done.

Think about it. To surgically remove a mutable variable out of a piece of code you must not just cut out the variable but you must also bring every function or procedure that mutates that variable along for the ride. If your function has an entire network of functions mutating procedures than that function cannot be divided into smaller functions.

An immutable function can be cut into a thousand pieces and rebuilt in a different way. This is the best known solution for program design and organization.

The real problem is when you get to scale. At a company like Facebook or Google, it seems like you end up spending half your time simply looking for similar code that other developers wrote to solve a similar problem so that you can avoid duplication in the codebase. When you have tens of thousands of developers that becomes VERY challenging.

Code duplication is one of the biggest mistakes I made as a new developer. As you became a better engineer, you learn how to simplify and extract common patterns into shared classes/libraries/components to avoid duplication. Code duplication is almost a cardinal sin due to the complexity and bugs that tend to result.

I use a totally different rule, a rule of C: if you feel that a code block needs a comment, extract it into a function with descriptive name.

With this rule duplicates rarely happen anywhere but in utility functions that extend the standard library.

Here's a discussion from last month about an encounter I had with this rule:

https://news.ycombinator.com/item?id=22024294

I kind of hate stuff like this. Why three? Why not four? Is it three because the "rule of three" sounds quotable?

It's similar to KISS. Yes I get why simplicity is great but it doesn't mean it's universally great. Is this acronym telling me to follow some random principle because the acronym forms a word? Many Complex systems exist in the world as an antithesis to KISS, so this principle isn't universal at all.

Why not stop duplication in ALL cases? What is the statistical evidence or logical proof that justifies "three?"

The premise is that the second time you do the same thing, it's often unclear if it's really the same thing, or if it's just the same thing at the moment.

I stick with the Rule of Three as a rule of thumb, and it seems that the repeated logic diverges about as often as it converges.

Once you start thinking about a third implementation, it's time to put the logic in one place.

No kidding. It makes big assumptions about how easy and clean the deduplication is to do. Time constraints. How likely the code will be to change, converge or diverge in the future or be junked entirely, etc, etc.

I’m not sure why you can’t just say, “Deduplication is often good, but use your own judgement.” I guess it’s not as catchy?