I currently have the glorious task at work to port a legacy react/jquery/materializecss codebase to react/material-ui.
It’s a pain, and quite a big one. When I started this a few weeks ago I didn’t really realize what I was getting into — now I’m more aware then I was before, but I still feel like it will take a while to get this finished.A few days ago, after work, a coworker asked me why I was creating a _Button_
Component, which was the inspiration for this story.
In university, the lectures targeting code quality all promoted to practice modularization wherever possible. I could see the benefits of this approach, but I’ve never been fully aware of what happens if you start finding excuses for ignoring this basic coding practice. Writing small self contained components/functions has big advantages over writing monolithic `do it all` ones which I want to highlight in the following article.
The application i’m working on has code duplication all over the place huge React components which only differ by a few words in string templates. 100+ Line functions which only differ by an array selector. Eventually I’m exaggerating but you get the point:
Should I help fixing this or run?
Obviously I can’t blame the prior developers — I’ve done similar things in the past. Projects grow rapidly, get more complex then we think at the beginning and hasty design decisions bite our asses at some point. We suddenly have to hold on to some deadline for potential investors or have to pivot as requirements change dramatically. With a huge amount of pressure we tend to betray our own ideals and start to do (not only) things like code duplication, because we convince ourselves that it’s faster and we can refactor later — even if we know better.
It’s a legacy meteor application, so testing before 1.3 was a mess to even get started with. Suddenly the node environment changed super fast — to the better — but huge projects like this have a hard time catching up with scoped modules/es6/lodash/app-state/unmaintained-packages and other daily hickups.
We all know historical grown projects, they almost have to be messy. On the other side of the plate we can see it as a challenge — legacy code often contains interesting solutions for special cases noone would ever have on the radar in first place.
I don’t know a single person who would argue that code duplication is generally a good thing. Nevertheless I think it’s interesting to look at implications from code duplication we are not necessarily aware of.
As there is no central definition, testing gets basically impossible. When looking at the testing pyramid you would basically have to skip all the ground levels and start right ahead with integration testing. Using simple UI testing tools like snapshots is unimaginable.
In most cases you will not have a simple button which always has the exact same classes and properties. In our code base for example, some buttons contain a few extra classes when they are used inline and some contain additional classes for left over jquery selectors.
The problem with css classes is, that you have to remember them — especially when not following any guideline like BEM or SMACSS . This is not an easy task and instead of remembering them you instead look in the code which classes are used elsewhere. This obviously slows down the process and isn’t a good developer experience at all. It’s not transparent which classes exist for a specific element.
When instead having a component you can simply define possible properties and map property names to classes. It’s not that hard.
Code is maintainable when you can make simple changes all the time, without breaking things. Let`s assume I wanted to change something about the button… e.g. handle some onClick ripple issues, or `switch the underlying css framework` — You now have to find and change all the places which implement some sort of button — you will most probably miss at least one. With proper component structure it would be a ~one-liner.
The problem, especially with large, duplicated functions and components is not only that they are large and duplicated. The biggest problem is, that noone will have the courage to change things. Not because they don’t want to, but because they fear to break something, without noticing— everyone always only touches the code he/she really has to touch. So mess gets messier and the points mentioned above get worse and worse. It’s hard to escape this spiral once entered, as you have to convince your bosses to stop the train and let you do work which doesn’t necessarily affect the products visible value. There’s no direct ROI on cleanup work, so you have to fight for the time and resources.
Especially in web development code size is a major concern. It actually does matter if your bundle size is 200 or 400kb. So having a couple times
React.createElement(Button, { onClick: handleClick })
vs
React.createElement('button', { className: 'a lot of different classes', onClick: handleClick })
Will actually have an effect on code-size. Of course this is a super simple example not considering minifications and gzipping, but the advantages get more significant with more complex Components and functions.
Gzip: most web servers nowadays support gzip, which basically strips out duplicate strings (in depth explanation). This doesn’t invalidate the argument, but makes it less problematic in a lot of cases.
Prepack: There’s a pretty cool tool called prepack, developed by facebook, which could also help optimizing functions. Still you shouldn’t use code optimizers as an excuse to write bad code, but instead use them to optimize well shaped code.
Javascipt is interpreted, so I don’t need to compile it right? No. Actually you need to compile/transpile it for different targets. This is not only, but especially true for web facing applications. You usually can’t just write modern code and run it in the browser. You will most likely use some sort of build system which translates some language features to a lower level to make it executable by *all/old* browsers. Having non modular duplicated code will most likely increase build time and decrease DX.
The first time you do something, you just do it. The second time you dosomething similar, you wince at the duplication, but you do the duplicatething anyway. The third time you do something similar, you refactor.
Don’t wait till the above statement get’s invalid. Refactor your code as soon as you detect the flaw, **not till you find time to clean up**
. Nothing is more important than to keep code clean, maintainable and testable. Every time you postpone a refactor you actively create work, decrease product quality and developer experience.
When programmers lose the fear of cleaning, they clean! And clean code is easier to understand, easier to change, and easier to extend. Defects become even less likely because the code gets simpler. And the code base steadily improves instead of the normal rotting that our industry has become used to.
What professional programmer would allow the rotting to continue?