Can Disposable Code Outperform Reusable Code? A Deep Dive into Maintainability
The article argues that treating lines of code as a cost rather than a product encourages writing disposable, loosely‑coupled software, and it walks through practical stages—from avoiding code entirely to using feature flags—to build systems that are easier to modify, replace, or discard.
Introduction
Programming is often painful because every line of code incurs a maintenance cost. Reuse can become an obstacle when APIs change or third‑party dependencies evolve, especially in large systems where compatibility and module relationships grow increasingly complex.
The author proposes measuring code by the "lines spent" rather than "lines produced" and advocates building disposable software that can be removed cheaply, instead of striving for maximal reuse.
Guiding Principles
Duplicate yourself to avoid module dependencies, but do not duplicate the management of that duplicated code.
Layer code: build easy‑to‑implement but hard‑to‑use modules, then expose a clean API on top.
Isolate volatile modules from stable ones and allow runtime configuration instead of hard‑coding every option.
Do not try to achieve all the above at once; start small.
Stage 0 – Do Not Write Code
Code line counts (50, 500, 5 000, 10 000, 25 000, etc.) give a rough sense of effort; a million‑line monster is far harder to replace than a ten‑thousand‑line program. However, writing fewer lines does not eliminate work—preventing unnecessary code from being written in the first place is the most effective way to reduce cost.
Stage 1 – Copy‑Paste Code
Reusable code often emerges only after concrete usage examples appear. Simple file‑system reuse is already a form of reuse, so a little redundancy is healthy. Repeating code several times is acceptable; creating a library function solely to give a name to a usage pattern can make future changes harder.
Functions are called based on observed behavior rather than documentation, and removing code inside a function is easier than removing the function itself.
Stage 2 – Stop Copy‑Pasting
When copy‑pasting becomes frequent, it signals the need for a utility function (e.g., "open a config file and return a hash table" or "delete a directory"). These utilities often belong in a util directory, but a single monolithic util file tends to grow unwieldy and should be avoided.
Highly generic code—logging, third‑party APIs, file handles, process libraries—tends to be reusable and less likely to be removed. Collections such as lists, hash tables, and other data structures persist because their interfaces remain simple and their scope does not expand over time.
Stage 3 – Write More Boilerplate
Libraries reduce copy‑pasting but still require boilerplate to adapt them to specific contexts. Boilerplate resembles templates: each use involves small variations rather than exact duplication.
Protocols, wire formats, and parsers often need such boilerplate because they must balance business logic with flexible communication requirements.
Stage 4 – Avoid Templates
When a library must satisfy many requirements, wrap it in a higher‑level API. The Python requests library is a successful example: it wraps the more complex urllib3, presenting a simpler interface while hiding details.
Packaging one library inside another separates concerns: requests handles HTTP adventures, while urllib3 provides low‑level tools for those adventures.
Do not create separate /protocol/ and /policy/ directories merely for separation; instead, keep utility code free from business logic and build user‑friendly APIs on top of easy‑to‑implement libraries.
Stage 5 – Write a Large Chunk of Code
After copying, refactoring, layering, and templating, you may still need to write a big, messy block of code to finish a task. Business logic often contains endless edge cases and quick hacks, which is acceptable for exploratory work.
Sometimes deleting one large error is easier than fixing many small intertwined ones. First‑time developers should boldly write “garbage” code to discover module boundaries; only later can they refactor.
When you know which parts will be discarded or replaced, you can safely take shortcuts, especially for one‑off client sites or event pages.
Stage 6 – Split Code into Small Pieces
Large code blocks are cheap to write but expensive to maintain; a simple change can ripple through almost every part of the codebase.
Adopt loose coupling: design modules that hide internal decisions from each other. As D. Parnas noted, isolate design decisions so they are invisible to other modules.
Separate code based on lack of shared state rather than shared functionality. Build modules for ease of writing, maintaining, and discarding, not merely for reuse.
When a module does two things, changing one often forces a change in the other. A simple component with a clear interface is usually easier to use than two tightly coupled components.
Loose coupling can be as simple as a single runtime flag or a command‑line switch. Examples include Windows internal vs. external APIs, HTTP caching via CDNs, and HTTP status codes (400 vs. 500) that let clients handle errors without code changes.
Erlang/OTP demonstrates robust error handling with supervision trees: each process is monitored by a supervisor that restarts it on failure, embodying the “fail fast, restart” principle.
The end‑to‑end principle suggests handling errors at the system’s outermost layer rather than deep inside, reducing the need for duplicated error handling.
Stage 7 – Continuous Coding with Feature Flags
When new code does not need to consider existing code, testing ideas becomes easier. Feature flags allow changes without redeployment, separating feature release from code merge.
Google Chrome illustrates the difficulty of merging long‑lived feature branches; runtime toggles enable incremental changes without affecting the rest of the codebase.
Feature flags are not merely command‑line switches; they decouple feature rollout from deployment, crucial when updates take hours or days.
The goal is a feedback loop, not endless iteration. Modules should isolate components to handle change, not just to enable reuse. Writing extensible code assumes correctness in three months, whereas writing disposable code assumes the opposite.
In summary, good software is designed to be easy to discard. Legacy code that does not block progress is the hallmark of well‑engineered systems.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Art of Distributed System Architecture Design
Introductions to large-scale distributed system architectures; insights and knowledge sharing on large-scale internet system architecture; front-end web architecture overviews; practical tips and experiences with PHP, JavaScript, Erlang, C/C++ and other languages in large-scale internet system development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
