It's Ok to Copy a Little Code
- 4 minutes read - 750 wordsDRY. Don’t Repeat Yourself. It’s a programmer’s mantra. And it’s a great rule of thumb. In general we should work to avoid duplicate code, refactor to functions, and maintain a single set of logic.
And I don’t want to argue against that – within a single codebase. If you maintain and control all the code, you should strive to stay DRY.
The biggest way that this goes overboard is when people try to overgeneralize across projects. Then we end up with tiny “utility” packages for trivial functionality or sprawling frameworks.
Big Decisions
It’s tempting to reach for one of these packages – at either end of the
spectrum. It saves a little code, and it’s just a go get
away. But we
should pause and consider what happens when we go get
a package.
First, are the license terms acceptable? Is it using a standard FOSS license or is it something “weird” that you don’t really understand. If you’re pulling this into a corporate codebase, you may need to get extra special approval for “weird” licenses.
Is the package currently maintained? Is it active? Is it mature? Are there dozens (or hundreds) of open issues? You need to try to predict if you’re going to find bugs, and if so, if the maintainer is going to fix them – or even accept patches.
Are the docs good? Is there an active tag on stack overflow for questions? Other avenues to get answers if there’s any chance you’re going to have trouble with the package?
Maybe the package is under-maintained. That might be ok … is the code clear enough that you’d consider vendoring it and maintaining it yourself (even just for your own use)?
After the Import
Ok great. You’ve decided to import a package. It’s pinned in your go.mod and you have it under tight control.1
Does the package expose you to any security risks? Will a critical bug in the package cause you downtime? How are you going to monitor for new releases – or at least for security fixes? Even without security fixes are you going to plan to take upgrades periodically. (Upgrades aren’t necessarily “free” – there may be API or behavior changes that affect your project.)
Another class of risks is an over-dependence on a framework. This can be true even if the framework is your own!
Over-Fitting
I have done it to myself and I have seen it at multiple employers. A pattern emerges across projects, and then that pattern is refactored into a library that is used by all of those projects. Future projects come along, and they are a little different but you contort the project, or add some options to the library, or (likely) both.
Over time – sometimes even very rapidly – this reduces the quality of the projects, or of the library, or (likely) both. When you’re lucky, this happens right away – you realize during refactoring that project A and B are not actually that similar and the pattern only exists in spirit. A few hours (days?) of work is lost but the integrity of the projects are maintained.
So it’s important to realize: if there is some code that you need to copy from an existing project, IT IS OK if you copy it! It’s also ok if you feel bad about copying it. That small bit of shame and doubt that you experience will keep you honest for all the times that you really should stay DRY.
Summary
Don’t get me wrong. I use plenty of other packages in my projects. But I try to make a conscious decision about what I’m pulling in, and the quality and risks of that dependency versus the quality, time, and risk of doing it myself.
Coming Up
On Friday we’ll look at how to add pagination to our app with Gin.
I saw this Go blog post shortly after I drafted this article, but it wasn’t coincidental. Both were likely inspired by the latest in a series of npm supply chain incidents. The title of this article was loosely inspired by the same Go proverb mentioned in that blog post: “a little copying is better than a little dependency”. ↩︎