Why set code standards?
Code standards enforced by automated rule checks improve the readability and maintainability of code—as well as reduce the number of bugs. Set standards help programmers and teams establish self-improvement routines and healthy habits to follow. By following standards defined within the configuration, programmers are able to sharpen the skills for creating clean code. It’s critical for developers and teams to adopt the right mindset to learn and adhere to enforced coding standards rules.
Every developer loves clean code, yet not every developer writes code in the same way.
Just because committed code works does not mean the code is good. Developers tend to have their own habits for writing code. Not every programmer agrees on how to write clean, readable code—especially when there is no good definition of how clean code should look. In addition, developers use their favorite tools (e.g., IDEs, Text editors, OS, etc.)
For example, by using different IDEs alone, code will likely be formatted differently. Unnecessary code changes will appear in each pull request, stretching pull request review times. Consequently, formatting and personal preferences become debatable. “My IDE formatting is better.” “No mine is.” Who’s right?
My goal is to help developers work together to resolve formatting issues. There are a number of programmer-friendly tools that help developers validate and unify the code base automatically without the need for additional documentation. The tools and tactics work for all team members (new and existing) since the setup is administered for everyone working on the project. Setting up standards for the entire team working on the project helps the team operate as a group, using shared rules and configurations without introducing personal preferences on styling which could worsen the readability and maintainability of the code. In this article, I’ll share my tips and tricks to improve project maintainability.
7 tactics to improve and maintain code quality
Below are recommendations on how EditorConfig, linters, prettier, git hooks, README files, Node.js, and check-dependencies help enforce code standards and maintainability. The examples use Angular using Typescript with NPM with GIT. The tactics shown apply to pretty much any framework, library, and programming language.
1. EditorConfig is a code styling configuration that gets rid of simple mistakes such as different indentation type usages.
The configuration allows developers to define spaces for indentation and the number of spaces used (e.g., 1, 2, 4). The .editorconfig file configuration and IDE program the code automatically. You can define a variety of rules to ease your daily workload.
Below is a simple example of an editor configuration file.
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
Example of .editorconfig file
2. Linters analyze the source code, provide tips, warnings, or errors depending on the preferred configuration.
{
"rules": {
"max-line-length": [true, 140],
"no-console": [true, "log", "debug", "info", "time", "timeEnd", "trace"],
"quotemark": [true, "single", "avoid-escape"]
}
}
This is an example of TSLint configuration (tslint.json)
For Angular, we use TSLint (which will be replaced with ESLint for JavaScript) to verify a Typescript code base. The above example uses 3 simple rules:
The code line cannot be longer than 140 characters.
String definitions use single quotes instead of double-quotes.
We cannot have console object functions.
Most modern IDEs pick up a configuration and show errors, warnings, and even might suggest fixing the issue automatically. Each team should work together to decide what rules the best suit them and their project. Alternately, teams leverage premade rules from the internet, tweak them, and use them instantly. While the example I shared uses Typescript, there are other static code analysis tools for front-end and back-end to choose from such as:
JavaScript – ESLint
Cascading Style Sheet – stylelint, CSSLint, Scss lint, Sass lint, etc.
C#, StyleCop, ReSharper, SonarQube A
All you need is to search for static code analysis or linters…which brings me to prettier.
3. Prettier makes code prettier and actually formats code for you.
It’s straightforward to set up and can be used alongside linters. While prettier reformats the code base, it doesn’t apply any quality code rule checking. By comparison, linters (when set up properly) do.
Combining these tools can be powerful. Prettier automatically adds syntax, while linters provide information about code quality violations (e.g., not providing types, no invalid regex expressions, and many others). Both tools require manual user interaction. Linters show errors or warnings that require manual fixes. Running prettier requires manually executing commands.
While it’s possible to add rules or even some sort of feature documentation in linters and prettier, it’s still difficult to track whether these tools are being used and code is being merged to the repository. Even if the code works and compiles during a pull request, people could still miss one or two places where code is not formatted, and that code would be merged after approval. Your teammates might be in a hurry, forget to do something, and not run commands for proper code analyses. Enter Git hooks.
4. Git hooks automatically execute custom scripts on specific actions.
Since Git is my source control management tool of choice, I want to talk about Git hooks. When using another SCM, there is a great chance it supports some sort of hooks too. Git hooks are a powerful feature that allows you to fire off custom script when certain actions occur. You can execute linter and prettier commands on certain actions within the repository to make sure that code is verified and follow the rules which are defined in the project.
Git hooks can be set up by directly configuring Git itself. You can also use other tools to simplify the process. I would recommend using a package called husky which really simplifies hook configuration. Best of all, its configuration is simple and coupled into package.json. The configuration becomes part of the development environment when running the first npm install, and the project encompasses the whole setup including code styling rules. Isn’t this amazing?!
"husky": {
"hooks": {
"pre-push": "npm run tslint && npm run stylelint"
}
},
Husky configuration in package.json
This simple fragment ensures whenever code pushes to a repository tslint and stylelint commands execute. If any error messages return, pushes are canceled and errors appear in the console’s output. In other words, you have to fix any errors in order to push code. You can also run other commands (which you would have to run manually), before pushing code to automate other tasks.
Some linters require commands which allow you to scan and apply fixes to the code base. While adding a script automatically does fix code issues with pre-push hooks, I would advise not to do so. Why’s that? When your tools are able to fix the entire code base, running the command might change all of the files dependent on the established ruleset. Treat this as code change—meaning run full application testing to ensure nothing is broken. In some cases, the application might not even compile and requires manual fixing.
Why shouldn’t this be added to pre-push/pre-commit or similar hooks if all code base was linted already? When code is fairly clean, would linting make much of a difference? Technically linting would run for code changes you’re making, right? While this is correct, you would lose the benefit of self-improvement. If the code wasn’t written properly and requires fixing errors, you lose out on learning how to write code well. You’re relying on linter to hold your hand to guide you on rules you're missing out on adding to your muscle memory.
For some, adding Git hooks comes across tedious and annoying. However, the benefit is that it helps to unify processes between team members. You can find more hook options here.
5. README files, when well-written, save time.
Well executed README files include clearly defined sections such as:
Prerequisites to start the application. These vary per the project. Some start out of the box without additional work. Others require third-party tool installations, development kits, setup certifications, VPNs, application configurations, windows configuration, etc. Be sure to provide information on how to run the application with the fresh operating system so that new members run the platform by following tailored guidelines. All guidelines are dependent on each project.
Instructions on how to launch the application. While obvious to most, I have seen projects without instructions take days to set up instead of hours. This is important on larger projects. However, I would consider good practice on smaller projects too.
Requirements to troubleshoot common issues. Depending on the environment, OS (if you have the luxury to choose one) can cause issues because of missing requirements, apps, libs, tools, certificates, etc. Defining information in a README file leads to much faster troubleshooting response and resolution. This slightly overlaps with setting up proper prerequisites. They are related in a sense because, without proper prerequisites, you might run into problems. Some prerequisites at first appear to be requirements. However, after a year or two of working, you wouldn't need to check application prerequisites to run.
Testing solution instructions. When using testing solutions, write instructions on how to run them.
6. Node.js makes switching between nodes easy.
While some developers work on a single project and rarely switch node versions, others have multiple projects using many Node.js versions. Switching between node versions is extremely easy, but still needs oversight. The cross-platform tool works on UNIX, macOS, and Windows. While not required, ensure that the project always includes a file named .nvmrc to contain a Node.js version.
13.12.0
This is a .nvmrc file contents example.
Simply, install Node Version Manager (nvm) to use any number of node versions. First, type “nvm install 12.0.0” to install Node.js version 12.0.0. Then type “nvm use 12.0.0” to start using version 12.0.0. Each version running commands in the nvm list install Node.js version and mark currently selected.
# nvm install 12.0.0 # 11.4.0, 8.9.1 etc
# nvm list # would display installed versions.
# nvm use 12.0.0 # start using one of your installed node.js versions.
Sometimes switching between node versions fails. In the development environment (on a local machine), usually, you are not running the “npm install” command unless you make some version changes or are running project for first time. In these instances, your teammate’s versions might be slightly different than yours if one of you didn't run "npm install" on package changes. While application works, technically, you would be running outdated code. Check dependencies solves that issue if used with application run command. The failure usually stems from not running an npm install command to reinstall packages in node_modules.
# npm install
7. Check-dependencies check versions on the fly with a single command.
How convenient is that?! A package that actually does what it says it does…checks dependencies! This tool makes sure that the exact versions defined in package.json are installed in the environment. Consider adding check-dependencies to the script section before each command runs a package version dependent code, the application starts, unit test, and e2e tests. It’s not recommended to run before linting, prettier, angular CLI, or similar commands which may cause unnecessary overhead.
If your package.json does not include fixed version definitions, someone running npm installs on the same code base later than you could get different packages from what you have in your local machine (unless you re-run npm install, which is rarely the case from my experience). Typically, during deployment, everything is wiped when the npm install runs and installs a fresh copy of packages. There might be some exceptions such as caching, specific setups to fasten deployment time and fetching these packages. This differentiates working (local) environments from deployment environments and often creates issues commonly known as, "works on my machine."
NOTE: In package.json, you have the ability to set the exact versions for packages and various setups for ranges. When not setting it to the exact version, running npm install will fetch packages with different versions. For more information, go to https://docs.npmjs.com/files/package.json#dependencies.
Maintain code consistency
All of these tools take some time to run before git commit, git push, or any other hook you choose helps maintain code consistency within a project. Team working styles and locations vary. Whether working onsite, remote, individually or side by side with a team, maintainability of code takes time. Establishing code standards helps account for the varied working preferences and bridges possible gaps distance present. As mentioned earlier, these tactics require the right mindset for individuals and the entire team to develop a product. Applying rules consistently helps improve the entire team’s coding standards and enforces good habits for writing clean code. Hopefully, these tips will help solve and upkeep code cleanliness for your projects.
Additional resources:
https://en.wikipedia.org/wiki/Lint_(software) (While this does not offer the best sources, it’s wiki and explains the point.)
https://www.npmjs.com/package/husky