About the Author
My name is Nate Clark, and I’m a Software Developer 1 at FNC. I work for an internal services team called the Shops team. We manage and develop cross-application services for FNC’s client-facing applications. Last September, I left Mississippi State University’s Computer Science PhD program to start this job. Which means I have been doing this for less than a year, so take everything I say with a grain of salt. I’m probably wrong, but I’m trying to get better.
TLDR : Big or small… avoid boilerplate.
I’ve been rolling around the idea of reader sensitive practices vs writer sensitive practices. A great deal of our tooling is designed almost exclusively to improve our lives as code writers. Very little goes towards our lives as code readers. I think it is mostly because it is much easier to improve our lives a code writers. It is a lot easier to generate and edit a bunch of boilerplate than to find a way to capture the essence of a problem and distill it into some construct of your programming environment. This reality started to smell bad when I asked myself if these writer-sensitive features are detrimental to ourselves as readers. It is easy to overlook the fact that they generate work for readers of the code, because it is so little work for us as writers. Some of these trade-offs may be small, but over time they may become a non-trivial source of technical debt.
Software developers love to talk about technical debt. We love to talk about how we avoid it, how we reduce it, how that other jerk built it. Technical debt is very real and deserves attention. Poorly defined APIs can cost a tremendous amount of resources when some unspecified behavior violates a client’s expections. Highly coupled components can sink a development team in the sea of effort required to untangle the hairball. These sources of debt are worth studying, but I want to talk about one particular source of technical debt we often overlook.
All Code is Debt
Every character we write creates debt: every comment, every function, every class, every test. I don’t think any developer is surprised by that list of debt sources. We all feel the burden of synchronizing our comments and tests with our implementation. We all feel the burden of maintaining and extending our functions and classes. However, I think there is one source of debt that we tend to happily take on the chin: code generation.
By code generation, I don’t mean the code generation that happens in your compiler. I mean the code generation that happens when you say rails new super_cool_app
, or when your IDE asks you to tab to expand a snippet. These tools take some small debt (“rails new super_cool_app
“), and turn it into a larger debt with the same meaning. Of course, this isn’t perfectly fair. You can now edit your snippet or rails application in ways that weren’t accessible at expansion. However, my central point still stands: these tools create debt very easily. For small cases like snippet expansion, it is easy to feel ownership over the entire expansion. But it may be worthwhile to ask why anything should be expanded in the first place. What does the snippet do that your programming language can’t do for you?
Of course, debt isn’t death. Students, home owners, entrepreneurs, and plenty others take on debt to improve their lives. Debt is like pointers. Some great things are made possible by it, some people use it to great effect, but it is generally advisable to avoid.
Do Your Tools Minimize Code?
We shouldn’t have to fight against our tools to minimize debt. Various elements of the languages and frameworks we use cause us to create more debt than necessary.
If we go back in time, software developers in statically typed languages wrote the same functions and data structures over various data types. Now, most languages have generics, so the amount of debt required to create the same benefit is greatly reduced. More recently, higher-order functions have found mainstream popularity in places like C#’s LINQ and javascript’s Underscore.js. Sometimes we may need time to learn new techniques or tools, but higher-order functions have shown us we should we wary of boilerplate as small as foreach
.
Coaches and athletes often talk about trying to “play their game” rather than “play the opponent’s game.” Frameworks like Rails or Angular often buy you a lot of leverage, but they often do it by forcing you to “play the opponent’s game.” The framework makes a bunch of decisions for you, and you are now forced to react to its decisions. Sometimes this is a tolerable debt. Sometimes the framework’s worldview is a perfectly reasonable worldview for your application. Other times it may give you just enough rope to hang yourself.
I’m not advocating we ban snippets or for loops. However, I do think subtle ideas like this can change the way we look at our overall programming environment. Even if it starts with being suspicious of the very small things.
My Two Cents
- We should be wary of generated code.
- Good ways to avoid generated code may be outside of our comfort zones. We should explore different tools at every level (frameworks/libraries/languages/language constructs).
Thanks
Thanks to Sarah Odom, Dan Nemesek, Allen Thigpen, Bethany Cooper, John Marsalis, and Megan Clark for their comments and corrections on this article.