For a while now we’ve been working on re-building our legacy back-end codebase at 15gifts from Perl into Node.js, and one of the main factors that we wanted to achieve when undertaking this challenge was code readability and code shareability, allowing our front-end team to be able to understand, modify and work with our back-end code base, and where possible, be able to use code either on the back-end or the front-end (Which we have now successfully achieved!). Here are some of the conventions that we decided upon, along with examples of how we achieved them:
- Well defined, clearly named files
- Using variables and functions with meaningful names
- Immutability, and reducing side-effects
- Statelessness and pure functions
- Composition over inheritance
Well defined, clearly named files
Small file sizes
Small file sizes, optimally less than 100 lines, allow developers to quickly see the intention of a file and what it was designed to do at a glance. It also helps with solving diffs in merge conflicts.
Naming files based on what they do
Naming files explicitly by what they do helps developers to be able to see at a glance what their intention is. e.g question-finder.js is better than question-logic.js.
Easy to locate in the file structure
By naming files using a clear convention like this, it should be easy for anyone to find a certain file as they should all follow a similar format.
Only one or two public functions in each file
Inside the file, public functions should always be listed at the top, and there should only ever be one, or in some cases, two. Private functions should be listed underneath prefixed with an underscore.
Using variables and functions with meaningful names
Giving variables meaningful names helps developers to understand why they exist.
Naming functions by what they do, in as much detail as possible, helps developers to understand why functions exist and what they have been built to do.
Immutability, and reducing side-effects
Use const instead of var or let
Using const guarantees that a variable’s binding cannot change after it has been set. Using const in a codebase also implies to developers that a variable’s value cannot change. If all developers working on a codebase are aware of this, then it makes it easier to modify code with confidence. We do sometimes use let in our codebase, when we know that a variable may change. This is very rare though.
Use filter, map and reduce over forEach
Using filter, map and reduce instead of forEach makes it easier to ensure that a variable cannot change once it has been set.
Statelessness and pure functions
No global state
By avoiding the use of global variables and ensuring that our local variables have the correct scope, we can be confident that they are only created and initialised when they are needed. This makes it clear to developers what the intention of a variable is, suggesting that it’s value cannot change.
Where possible, avoid calling out to the database or external services
Pure functions which contain only logic and no calls to retrieve data from a database or external services are much easier to understand than functions which need to do this.
Sharing pure functions with the front-end
If a file only contains pure functions then we know that it can be shared with the front-end and it’s logic imported and used whether it is being run on the server or the client. This greatly helps with code re-use and speeding up development time.
Composition over inheritance
Not using classes means that functions can just be imported from files and called directly, rather than needing classes to be imported and instantiated. Instead of inheritance, common code can be separated into it’s own file and imported where it is needed.
Using a plugin based architecture means that code can be dynamically injected at runtime, depending on certain parameters. This helps us to share our codebase between different clients and channels with relative ease.