The One Tech Concept Everyone Should Learn
There is a mental model that has gained some traction recently called chesterton’s fence. The moral of the story is that we should figure out why something exists before we change or remove it. This is a useful piece of wisdom. Of course, the next question that naturally follows is “How?” How do we ensure that we are only removing redundant fences? How do we ensure there isn’t a bull waiting to trample us on the other side?
The answer to this question is not embedded in the mental model. But I think one concept from the tech world can facilitate answering the question of “How?”. It is a concept worth understanding even if you consider yourself nontechnical and never plan to get technical.
That concept is called refactoring.
Envisioning how entire systems operate is a difficult thing to do – perhaps near-impossible to do in totale. So processes of refactoring were developed to aid software developers in building external scaffolding around systems to verify, at key points, that the system is operating as intended. These specific processes ensure the behavior of a system won’t change while the system is updated internally.
Refactoring: What Is? 🐝
Refactoring code is often compared to editing a piece of writing. This is a useful analogy to get started:
When we write an essay or a blog post, we might start with a simple stream of consciousness to see what we come up with. Once there are enough words and ideas to play with, we might sort that stream into a coherent outline. Once structured, we’ll flesh out the body with full paragraphs, smooth out transitions, and hopefully not add too many adverbs1. Then we edit. Once we have a real draft, we’ll either wait a while before looking it over again or have someone else look at it for us. We hone our words to increase precision and convey the right meaning. Rinse, lather, repeat.
Writing code is, for the most part, quite similar to this.
The first priority is just to get a solution working at all and this might be accomplished in a stream-of-consciousness-like fashion. Outlining might be compared to designing and whiteboarding a solution; drawing out diagrams to represent how the system might look from a bird’s eye view. Further fleshing may then include hashing out finer details2 and adding comments to the code. Then we refactor. We determine what levels of abstraction make the most sense: Should I wrap this bit of logic in its own function? Should this be an API call that others don’t have to worry about? Once code is moved to a (hopefully) more appropriate level, we verify the system still operates as expected. Rinse, lather, repeat.
As useful as these parallels are, there are details that get lost in translation. Key differences lurk between the process of remixing words on paper and yanking characters around a code editor. Upon closer inspection, these differences can be nontrivial. And within those differences lie potential solutions that our society sorely needs.
Differences: A Close-Up 🐝
“Perfection is achieved not when there is nothing left to add, but when there is nothing left to take away.”
~ Antoine de st. exupery; wind, sand, and stars; 1939
- Make it understandable and mind your levels: The main purpose is to improve the readability (or efficiency) of code by placing logic at appropriate levels of abstraction.3
- Don’t add new features now: The goal is to improve the function of the system, not to change the system’s behavior.
- Verbalize assumptions and verify them: The process includes adding automated tests to validate the code’s functionality.4
1. Make It Understandable and Mind Your Levels 🐝
This step is almost identical between editing and refactoring – minus engineering optimizations that can occur while refactoring. This visual, illustrating the evolution of technology, exemplifies the end goal and what the process should feel like beautifully:
The aim for both editing and refactoring is to improve clarity, coherence, and conciseness. Topics of editing such as word choice, correct grammar usage, transition management, jargon explanation, and the like all have familiar parallels in refactoring: Variable naming, correct syntax usage, module interaction management, commenting code…
The one guiding principle I’ve come to rely on to make things more understandable is to answer the question: At what level should this live?3 The answer to this one question will guide you in making sense of multiple levels of your overall structure simultaneously whether you’re considering sentences nested in paragraphs in chapters, or functions inside of classes in modules.
2. Don’t Add New Features Now 🐝
Both editing and refactoring almost always result in the changing and deletion of content. But while editing may include additions, many coders advise refraining from adding new features while refactoring. We are bad at multi-tasking, so just don’t do it. The act of adding, and potentially changing the behavior of the system, should be performed completely separately from changes and removals meant to improve the system itself.
3. Verbalize Assumptions and Verify Them 🐝
Whether it’s editing or refactoring, the goal is to get some idea across as clearly as possible to others. When you re-word your essay, you’re aiming to add precision to your message so that others will be more able to grasp your meaning. While refactoring, we want to increase the clarity of intent and crispness of logic in a codebase, while ensuring our solution still works and solves the original problem(s). We can reasonably be more confident our solution still works as expected when we follow something like the process below:
- Develop an understanding of the problem space and how the codebase solves the problem.
- Write code that verifies this understanding (called “unit tests” or “automated tests”6).
- Make edits to the original codebase to increase the clarity of logic and intent (see 1. Make It Understandable above).
- Validate the newly written code against the verification code you wrote (by running the automated tests – and hope they all “pass”).
- Repeat steps 1-4 as necessary.
Having your future self or others read and edit your writing can act similarly to this process. And it is conceivable that a skilled editor’s process will mirror this portion of refactoring to the extent that it is possible – it is hard to beat automated tests.
But it is worth emphasizing the two steps I think are often missing in many editing processes:
- Step 2 gets you to think about the code’s purpose and forces you to make it explicit (with the writing of automated tests).
- Step 4 validates that understanding against reality (with automated tests).
Remodeling Fences 🐝
The process outlined above arguably revolves solely around verifying assumptions – and preventing disaster. This is because systems are extremely complicated. Alice could make a single change that percolates throughout the entire system, wreaking havoc on all the users downstream, without even knowing it. This is especially true of details that have faded into the background of normalcy. Perhaps that change removed a feature fundamental to Bob’s workflow – but he didn’t even know it was fundamental until it was broken or gone: It had always just worked. Similarly, we tend not to think about how and why cultural norms, laws, and regulations shape the way we live…Especially when they just work.
But this is a mistake and it is dangerous. We should get in the habit of making these norms explicit, to understand what’s going on under the hood, and verify that what we’ve verbalized is accurate and in accordance with reality. We should not only be doing this when we build web applications or machine learning pipelines, but in society at large. The processes of refactoring software could guide and inform the creation of a framework that helps us better innovate culture, society, and government.7
We must not forget that if we can’t describe why the fence is there, we won’t know what we will unleash when we try to take it down.
With regard to societal systems, this post is best read in conjunction with.
“Most adverbs are unnecessary.” ~ ↩
For example, say you wrote a dummy timestamp function to return the same time every time you called it in order to force uniform behavior. Once the desired function around that timestamp is complete, you might then replace it with the current time to actually get the desired functionality. ↩
There may be developers who disagree with this definition. True, testing is not always assumed in the steps of refactoring. But I would equate not using tests to not inspecting the other side of Chesterton’s fence. Say hello to Toro for me. ↩
“Automated testing” in software development is often compared to “double-entry bookkeeping” in accounting. ↩
Occasionally you will hear someone say they want to start society over from scratch. But even in software, experienced developers will vehemently warn against this. If we can’t reasonably expect to start a technical project over with less effort than it takes to refactor it, how do you expect to do so with something as vast as society? ↩