Driving Consistency with Custom ESLint Rules
I spent some time over the weekend writing an ESLint plugin template. What is an ESLint plugin and how can it help drive consistency in a repository? Well you're on the right post!
If you've worked with me in the last 5 years or so, you've probably heard me harp on consistency being key to the long term success of a codebase. Consistency is extremely important in many areas of software development and UI/UX. In the UI/UX realm, your customers expect visual consistency in a product. They expect similar flows to be consistent across different areas of the product. Without it, the experience feels disjointed and your product may become annoying to use. In software development, without consistency, refactoring code is like untangling a massive, impossible nest of spaghetti while also trying to find a needle in a haystack.
One of the strong opinions I have is that I'd rather have 10 consistent "areas of improvement" in a codebase than 10 one-off solutions for that problem scattered about. It's way easier to collect the consistent patterns and arrive at an improved solution, or maybe a nice abstraction. It also makes refactoring way easier - you know of all current places doing "the thing" because you can search for that pattern fairly easily. With the one-off approach, it can be extremely difficult to track all of those use cases down. This leads to hidden tech debt. Ouch.
For the past 6 months, I've been working with an incredible team. We grew very rapidly to 8 engineers including myself. As we kept growing, it was clear to me that we needed to establish and enforce patterns in our repository to ensure we maintain a high quality bar while also ensuring the codebase was easy to contribute to. Without patterns and guidance, the codebase would quickly become chaotic. It would make it harder to onboard new engineers. Different, inconsistent patterns would pop up over time. It would make PR reviews a lot harder. Not being able to automate some of the decisions we've previously decided on lead to rehashing the same conversations with different people in different PRs.
One way I solved this was driving consistency via Prettier and ESLint rules. Automated tools are always a great place to start. Prettier formats the code, while ESLint enforces coding patterns. Using off the shelf rules are great, because it's less work for you. There are a lot of great ESLint plugins you can use like unicorn; however, you may reach the point where what you want to enforce is very specific to your codebase's needs. This is where writing custom ESLint rules come in.
In my opinion, a great sign of a well established repository is popping into it and not being able to tell which developer wrote which file. Each file follows a similar format and the patterns are consistent across files. When I look at a repository, I prefer everything following the same patterns. This happens quite a lot in large open source projects. They have so much automation and tooling in place to help drive consistency - it makes it a treat to contribute to that repository because there's less to think about.
I strongly believe custom ESLint rules are extremely helpful for any codebase. They help drive this consistency I've been harping on above. It's even better when the code provides autofixes - then the developer doesn't even need to think about how to format or structure their code. It gets fixed for them automatically by running a command!
I've open sourced a repository on GitHub at https://github.com/ynotdraw/example-eslint-plugin. It's a walkthrough of how to write your own ESLint plugin and rules. In the README, I talk through everything as best I can to hopefully share some of the knowledge I've collected. Some of it may be incorrect, or there may be other ways - this is why I open sourced it! Please feel free to put up a Pull Request if you find improvements.
Overall, consistency is very important to me. It helps a codebase from spiraling out of control. It makes contributions so much easier. It serves as documentation in a way for how the repository wants code written. It makes developer's lives easier by autofixing code for them to match the patterns we decided on as a team. It makes refactoring easier. Leaning heavily on Prettier and ESLint goes a really long way. And whatever areas software can't fix automatically, using documentation is a great solution too.