Draft Small Components
There’s a nearly universal joke in the world of software development:
“I was looking through some of the worst code I had ever seen, so bad that it made me question how anyone could have written code that awful. Then, I checked who wrote it and saw that it was me.”
It’s stuck around because it reflects an unavoidable truth that every developer encounters; in an effort to constantly deliver new features on tight timeframes, code is written, hopefully tested, then promptly forgotten until it needs to be extended.
The best practice for any kind of development is to spend a bit of time ensuring that code is written from the perspective of extensibility, to minimize the amount of time that has to be spent relearning code when it needs to be changed. The secondary goal of any code that is written (after accomplishing the actual business requirements of that component) would be to be immediately comprehensible by any reader, be that a new developer or the original author returning to that code after a month or two away. The longer a developer needs to spend parsing a chunk of code, after all, the easier it is to reach a flawed understanding of its meaning, and confusion leads to frustration, mistakes, and stress for the developer, product owner, and the client.
There is a lot of debate over what makes code easier to understand on face value, to the point where entire books on coding principles have been written, detailing specific rules on structure and naming conventions that help improve readability. More so than any specific rule, however, there’s a broad axiom that seems to cover the intent of all of these principles: keep your code small.
Small code, in this case, means that individual components and files should be limited in their literal length, as well as their intent. By keeping the actual length as small as possible, we are forced to limit the amount of different responsibilities that a single element within a program is required to handle. This ensures that this particular element is isolated, allowing the code itself to cleanly describe its own function without other unrelated functionality concealing itself.
It’s nigh impossible to keep code small when a number of responsibilities are grouped in a single piece of code. It’s very easy for a developer not to notice that a particular component is handling multiple unrelated purposes, and for changes to introduce defects in unrelated features. These defects may not even be caught by the developer as they test their new work, since they wouldn’t intuitively realize that they were making changes to anything other than their area of focus. Even if the new defects are caught before they’re encountered by users, the developer spends extra time working on functionality that is unrelated to the actual changes they intend to make. By spending additional time to ensure that functionality of components is isolated when it is new, each time the code is altered later, time is saved and confusion is prevented.
An unintended side effect of large components with grouped functionality is repeated code. Extremely large components tend to render the same component or perform the same function several times, and the nature of web development is that it is often easier, in the moment, to simply reimplement a code fragment again instead of walking through the boilerplate required to extract that functionality. This almost ensures code drift will occur; each time the component is updated in one place, every usage must be updated. Even for the initial developer, who knows that an update should affect multiple parts of the code, it’s all too easy to miss spots. By extracting these objects and bits of functionality early, you ensure that any changes are applied in all usages, and you encourage other co-developers to reuse that same bit of code when a similar solution is required. This also naturally discourages components from becoming too broad over time, as these small, single function components can’t be given a large number of one-off exceptions to their functionality without adversely affecting a large number of usages across the program.
One seemingly minor advantage of small components comes in the form of naming. Naming code is a often discussed topic, usually with the punchline that “naming variables/components is the hardest thing a developer has to do.” The reality is that it’s hard to give indicative names to code when it’s trying to be multiple things. A single, obvious responsibility lends itself to a long, but descriptive and immediately informative name. The broader a component’s responsibilities, the more likely it is to have a name like the infamous “data manager”, which tells the reader literally nothing and requires a further dive into the code to even begin to understand what it might do.
Broadly speaking, the focus on small components forces the developer to consider what they are writing from the perspective of the future. By ensuring that the code can be quickly understood and easily reused, the developer allows it to be extended and altered by other developers who won’t have the benefit of knowing the mindset it was written in. It’s certainly less natural, since developers are often in the mindset of making “quick fixes,” where they aim to change as little code as possible, and this is often brought about by large components filled with many responsibilities. Rather than encouraging making safe changes, developers should want to make good changes that last.