Some tools exist to help developers keep the code consistent and easier to understand.
I want to show you various tools to help you write better quality software.
Note: this article was created with web developers in mind, but non-web folks can also find some exciting things.
Table of Contents
Extensions
Those are the easiest ones to set up. The huge advantage is that they're global, so you can use them everywhere without installing them in the project.
These are the ones that I use (and I can freely recommend them):
SonarLint
A great tool that gives you instant feedback based on your code.
Can suggest some improvements and catch potential bugs.
Abracadabra, refactor this!
Enhances the refactoring options in VS Code, which makes the refactoring code easier.
This won't automatically make your code better, but it is a helpful tool to have.
JS Refactoring Assistant (paid)
This is a combination of the previous two extensions.
It suggests refactoring changes to make the code cleaner and easier to read.
Tools
ESLint
A must-have utility for any JavaScript project. It helps to keep the code style consistent, and it can prevent different anti-patterns and common mistakes.
To make work with ESLint easier, there's the ESLint extension for VS Code, which includes the commands to check the logs and restart the server, which makes it easier to debug the configs.
My personal config files
- .eslintrc.js
module.exports = {
'env': {
'browser': true,
'es2022': true,
'node': true
},
'extends': [
'eslint:recommended',
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/strict',
'plugin:react/jsx-runtime',
'eslint-config-async'
],
'overrides': [
],
'parser': '@typescript-eslint/parser',
'parserOptions': {
sourceType: 'module',
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
'plugins': [
'react',
'@typescript-eslint'
],
'rules': {
'quotes': [
'warn',
'single'
],
'semi': [
'warn',
'never'
],
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-unsafe-call': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-unnecessary-type-assertion': 'warn',
'no-shadow': 'error',
'prefer-const': 'warn',
'no-console': 'warn',
'no-debugger': 'warn',
'no-magic-numbers': ['error', { ignore: [1, 0, 404, 200, 500] }],
'no-dupe-else-if': 'error',
'max-depth': ['error', 4],
'max-lines': 'warn',
'max-params': ['error', 3],
'no-unneeded-ternary': 'error',
'react/boolean-prop-naming': 'error',
'react/jsx-max-depth': ['warn', { max: 5 }],
'import/no-default-export': 'error',
}
}
I'll describe the most important rule I decided to use:
- no-floating-promises - enforces you to handle the promise. I had multiple situations that I forgot to use await which led to bugs and false positives in my tests.
- no-unsafe-call - disallows calling any value that is typed as any.
- no-unsafe-return - prevents from returning the any type from the function.
You can read about the rules here.
tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"types": ["node", "jest", "@testing-library/jest-dom"]
},
"include": [
"src",
".eslintrc.js",
],
}
You can adjust it to your needs.
Husky
Pre-commit hooks are (mostly) cool.
Theo - ping.gg wouldn't like this, but I don't care.
Sometimes developers forget to run tests, or you accidentally push some invalid code to the repo.
Husky helps run the hooks (some tasks) that will be run, i.e. whenever you commit or push the changes to the repo.
This forces the validation to run before the code is pushed to the repo.
However, do not run heavy tasks like testing E2E or the whole project cause it will be time-consuming and highly annoying.
My personal recommendation:
- Pre-commit hooks for commit messages.
- Pre-push hooks for linters, unit and integration tests.
Commit messages
PLEASE, please, make your commits consistent. This makes the Git history and pulls requests easier to track and potentially rollback the changes.
My go-to choice is Conventional Commits - give it a shot and connect it to Husky via Commitlint.
CI/CD (GitHub Actions, CircleCI, etc.)
They are helpful to automatically deploy your app (for example, to preview the deployment to test it manually) and run all the tests and linters to check whenever the branch is ready to review.
This is the most straightforward workflow file for GitHub Actions to run the tests whenever the pull request is created.
name: pull-request
on:
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3.3.0
- name: Set up node
uses: actions/setup-node@v3.6.0
- name: Install dependencies
run: npm install
- name: Run tests
run: npm test
You can adjust it to your needs.
Tests (units, integrations and E2E)
Those are the ones that will save your code from regression whenever you decide to refactor the code.
What tests should be like?
- Avoid the worst assertion in the world - comparing booleans or using methods that need to return more context when the error occurs.
Like expect(condition).toBe(true)
or expect(items.length).toBe(5)
- They should make sense 😮💨 (writing tests just for the sake of testing doesn't make sense at all)
- They don't care about the code implementation
- Check both the "happy path" and "sad path"
- Readable, easy to understand
- Shouldn't slow the development
You can learn about these here: bewebdev.tech - Testing.
Principles
No tool will save you from writing readable code.
- Creating reasonable abstractions (i.e. by using Bridge or/and Adapter patterns)
- Using relevant data structures to the problem you are trying to solve
- Breaking up the larger part of the code into smaller ones
- Writing declarative code by using built-in functions
- Taking care of immutability in your functions
- Good, consistent naming conventions
- Writing code that is easy to test
- And probably many, many things that I probably forgot to mention 😅
All of these will make your code significantly easier to maintain and scale.
You can learn about these topics here: bewebdev.tech - Clean Code and bewebdev.tech - Design Patterns.
bewebdev.tech is a resource hub for web developers that helps them to keep up with industry needs.
Top comments (1)
@willaiem I see that you use
max-params
rule, I've a plugin that takes limiting function parameters and arguments to the next level! 😊Wanna give it a try? Check it out 👇
Limit the number of function parameters and arguments with ease and flexibility!
I would be thrilled to get feedback from you or answer any questions you might have about it 🙂