visit
It is no secret that developers have to ponder over numerous everyday tasks. Part of their thoughts is always dedicated to various routine matters, such as "In which module should I place this function?", "How should I name this variable?" or "What should this variable do?". Despite these questions appearing simple and trivial, they consistently consume mental resources. However, if there are ways to simplify this process by automating some routine tasks, thereby focusing attention on more critical matters, it undoubtedly makes sense to take advantage of them.
In this context, naming conventions play a crucial role. Acquiring knowledge and employing these conventions judiciously contribute to enhanced code readability, simplifying comprehension, and reducing the developer's cognitive load. However, not all developers are aware of, and sometimes forget, how to correctly apply these conventions, mindlessly diverting their attention to seemingly more critical matters. This, in turn, complicates the readability and understanding of their code, making ostensibly "more critical" tasks more intricate than they could actually be.
Below is a screenshot from a real project I had the opportunity to work on. Take note of each import. Try to guess, based on the names, what item we can obtain from each of the imports.
In this article, we will explore most of them, learning or reminding ourselves which principles are best to follow when choosing names for the constructs you write. This will help both you and anyone who works with your code in the future – whether it is your team or yourself after some time – avoid guesswork and enhance the readability of your code.
On the internet, there is a considerable number of articles written about best naming practices. Well-known conventions emphasize that a name should be short and, more importantly, understandable. The name should be specific, correspond to the context in which it is used, and convey the meaning of the code at a glance. In reality, these conventions can and should be applied not only when working with JavaScript but also with any programming language. In these general rules, I would like to focus on some specific details in conventions that are widely known but often forgotten or not fully adhered to.
Uniformity. In JavaScript projects, there are numerous conventions, but the most valuable one to adhere to is maintaining a consistent coding style within a single project. Individuals involved in different projects know better than anyone that writing styles can vary significantly between projects.
Despite the convention in the JavaScript language to write in camelCase
, it is worth noting that nothing is preventing the use of, for instance, snake_case
. As already mentioned, this notation might be more familiar to many other programming languages and some teams. There might be situations where a naming case different from the style of your application starts creeping into your code.
// Bad
const hasAccess = checkHasUserAccess(session.current_user) && checkIsPageVisible(session.current_page)
// Good
const hasAccess = checkHasUserAccess(session.currentUser) && checkIsPageVisible(session.currentPage)
// Also good, if such a naming case is preferred
const has_access = check_has_user_access(session.current_user) && check_is_page_visible(session.current_page)
There are several ways to address this issue, ranging from using renaming with , , using mapping functions (e.g., ), and more advanced approaches like utilizing the design pattern.
English only. Despite the fact that you can use non-Latin letters when writing in JavaScript, it is not recommended to do so. While there will not be any programming errors, it is strongly discouraged to use any languages other than English. Even if you are writing something for yourself, every piece of code has the ability to "live long", and there may come a time when you need to share this code with other developers. For other developers, especially from different countries, it will be difficult to understand what is happening in the code.
When emphasizing the use of only English, people often think only about keyboard layout. However, one should not forget about abbreviations and the general use of short forms for names. They should only be used with commonly accepted words, such as idx
, err
, evt
, xhr
, src
, and other historically accepted words in the community. In other cases, the use of abbreviations and short forms of words is strongly discouraged, as they often lead to confusion, and deciphering them can take a lot of time.
// Bad
const 新規ユーザーのウェルカムメッセージ = 'こんにちは'
const usrInf = { fName: 'John', lName: 'Doe' }
const isAdult = a >= 18
// Good
const newUserWelcomeMessage = 'こんにちは'
const userInfo = { firstName: 'John', lastName: 'Doe' }
const isAdult = userAge >= 18
It is also recommended to enable a spell checker in your code editor, which will highlight grammatical errors in words. In many editors it is enabled by default, and for some, you may need to install an extension for VS Code. Today's spell checkers are smart enough, and most commonly accepted abbreviations and short forms of words will not be flagged as errors.
Not only camelCase
. Many resources emphasize using camelCase
notation exclusively when writing in JavaScript. However, this is not entirely accurate. The recommendation is more about adhering to the notation used by the language itself. While a significant portion of the JavaScript language is written in camelCase
, it is not universally so. Here are a few examples:
parseInt('18') // camelCase
new RegExp('[A-Z]', 'i') // PascalCase
Number.MAX_SAFE_INTEGER // PascalCase + UPPER_SNAKE_CASE
As we can see, JavaScript incorporates not only camelCase
notation but also some others. All classes and constructor functions in JavaScript are written using PascalCase
notation. When declaring custom classes and constructor functions, it is customary to follow the same naming convention as the language itself. The same applies to constants, representing fixed values. Both built-in JavaScript constants and those created by developers are conventionally named in UPPER_SNAKE_CASE
.
Follow readme.md
. Suppose you are working with a network request library like , and the library's README recommends using the standard name axios
for imports.
import httpRequester from 'npm:axios'
// Somewhere very far at the bottom of the file
httpRequester('//example.com')
Changing the name from axios
to httpRequester
can cause confusion and errors, as other developers working on the project and expecting the standard name axios
may encounter issues. Therefore, it is important to adhere to the recommendations set in the library's README to ensure compatibility and code understanding.
At first glance, the example with the axios
library may seem harmless, but similar renaming scenarios can arise with the use of more complex libraries and frameworks deeply integrated into a project. Teams that adopt meaningful renaming practices will need to create their own documentation because new team members will not find sufficient information in the official documentation to understand the changes in the technology's usage style.
Typically, meaningful renaming into something more abstract, is used in conjunction with the . However, it is important to understand the fine line here. You and your team must have a clear understanding of what you are doing and why, as there needs to be a balance in adopting such practices.
No data types. It can be tempting to include data types in variable names, but succumbing to this temptation often adds unnecessary semantic load to the name. For instance, using the name arr
for a variable containing an array can lead to conflicts if a variable with the same name is used elsewhere. Moreover, the name arr
does not convey meaningful information. Making the name more specific, like userArr
, is an improvement, but it does not address all the issues.
// Bad
const userNameMinLengthConst = 3
const userObj = getUser()
const getUniqueUserNames = (arr) => Array.from(new Set(arr))
// Good
const USER_NAME_MIN_LENGTH_COUNT = 3
const user = getUser()
const getUniqueUserNames = (names) => Array.from(new Set(names))
For each language construct, whether built-in or custom data structures, there exists a conventional way to indicate the intended data type of the value. This is reflected in the name of the construct, which should align with both the established community conventions and those within your team. It is advisable to prioritize community conventions, as explaining something widely accepted is generally easier than introducing something entirely unique. In the case of community conventions, you can refer someone to an internet article, while for team-specific conventions, you might need to write that article yourself.
For instance, in JavaScript, an object can serve as an enum
, map
(dictionary
), graph
, and more. The number
data type in JavaScript includes handling both integers and floating-point numbers. Depending on specific requirements, JavaScript data types and structures can be flexibly adapted for use, providing JavaScript with versatility and power.
Booleans. Names of boolean values should start with an affirmative prefix, meaning the prefix should answer the question "yes." Although there are several affirmative words (should, can, will, etc.) that answer "yes," it is advisable to prefer the two most common ones – is
and has
. While using other affirmative words will not be considered an error, their use should be treated as an exception, and if possible, it is better to avoid using them.
An important addition to this convention, often overlooked, is that the affirmative prefix should not include a negation. The reason behind this is that the negation operator (!
) is most commonly used with boolean values. Therefore, a value named something like isNotAllowed
with the negation applied to it !isNotAllowed
can be quite misleading. Do not believe so? Then try quickly figuring out what !!isNotAllowed
(double negation) equals. Even if you manage to do it quickly, imagine working with real code where all these negation inversions are scattered throughout the file. Keeping track of such logic, especially when there is a lot of it, can be quite challenging. In such cases, it is better to alter the logic of evaluating the boolean variable using a positive affirmative in its name.
// Bad
const userLogin = user !== null
const user = {
friend: false,
}
const isNotUserRemoved = userActionState !== ActionState.REMOVED
if (isNotUserRemoved) { /* some logic */ }
// Good
const isUserAuthorized = user !== null
const hasUser = user !== null
const user = {
hasFriends: false,
}
const isUserRemoved = userActionState === ActionState.REMOVED
if (!isUserRemoved) { /* some logic */ }
Along with the convention to use affirmative prefixes for boolean names, we diverge from the , as it advises against using these prefixes for boolean names. This contradicts the idea mentioned earlier to prefer conventions used by the language itself. However, there are always exceptions, and this is one of them. It is normal since the way specification authors think may differ from the community's perspective.
Functions & Methods. The name of a function/method should be a verb and correspond to the action it performs.
// Bad
const userNaming = (name) => name.charAt(0).toUpperCase() + name.slice(1)
const user = {
name: 'John',
action() {
console.log('Hey')
},
}
const userPermissions = (userId) => permissionRepository.getByUserId(userId)
// Good
const getCapitalizedUserName = (name) => name.charAt(0).toUpperCase() + name.slice(1)
const user = {
name: 'John',
sayHello() {
console.log('Hey')
},
}
const getUserPermissions = (userId) => permissionRepository.getByUserId(userId)
Although the naming convention for functions/methods may seem simple at first glance, their naming is the one that has the most exceptions and other conventions.
Number.isNaN()
, Array.isArray()
, salary.toFixed()
, , etc.).onBtnClick
, onImgMouseOver
, etc.).const users = (state, action) => { /* some logic */ }
), functional components in (const UserDashboard = () => { /* component */ }
), and many other exceptions and conventions proposed by libraries and frameworks.
Collections & Iterators. The naming convention is as follows – if or are used, for example (Array
, NodeList
, FileList
, etc.), the name should be a plural noun. Otherwise, if (Set
, Map
) are used, and we are only interested in values that we can obtain by keys (usually using Array.from()
or ), the naming convention remains the same – the name should be a plural noun. However, if the keys are also important to us in keyed collections, then such collections should be named using a singular noun, and at the end of the name, add one of the prefixes that signifies a set of something. For example, Collection
, List
, Group
, and others.
// Bad
const userRoleArr = Object.values(UserRole)
const usersAccordionList = document.querySelectorAll('.users-accordion__list-item',)
const groupPermissions = new Map(Object.entries(Object.groupBy(permissions, (permission) => permission.group)))
// Good
const userRoles = Object.values(UserRole)
const usersAccordionItemNodes = document.querySelectorAll('.users-accordion__list-item',)
const permissionsByGroupList = new Map(Object.entries(Object.groupBy(permissions, (permission) => permission.group)))
Classes. When working with classes, it is important to follow several conventions. Here is the list:
PascalCase
for the class name, following the same case style that JavaScript uses for classes and constructor functions.// Bad
class userService { /* class members */ }
class Permissions { /* class members */ }
class UserAuthPopup {
isPopupOpen = false
openPopup() {
this.isPopupOpen = true
}
}
// Good
class UserService { /* class members */ }
class PermissionService { /* class mmebers */ }
class UserAuthPopup {
isOpen = false
open() {
this.isOpen = true
}
}
If there is a temptation to use the class name in the names of its members, it is likely that the class is overloaded with logic or is responsible for more things than it should be. In such cases, a violation of the can often be observed. To avoid such situations, it is recommended to create additional classes and use inheritance or dependency injection to limit the responsibility of each class.
Constants. Used to describe values that are known before the program's execution and that should not change during its execution.
Constants are a crucial and widely adopted approach to organizing a program's code. Regarding naming conventions, there is an important agreement among developers: the name of a constant should be written using the UPPER_SNAKE_CASE
notation.
// Bad
const userFailingLoginAttempts = 3
const USER_ROLE = calculateUserRole(user)
let userDefaultAuthMethod = 'mfa'
// Good
const USER_FAILING_LOGIN_ATTEMPTS_COUNT = 3
const userRole = calculateUserRole(user)
let USER_DEFAULT_AUTH_METHOD = 'mfa'
Although there are several ways to declare a variable in JavaScript, and for constants, it might seem logical to use only the const
keyword, which during program execution, as some teams prefer not to worry about choosing the keyword for variable declarations. They may use automation tools like ESLint to enforce the use of only the let
keyword for variable declarations in the program code.
Enums. Also known as enumerations. In JavaScript, this data structure is used to enumerate a set of fixed values.
// Bad
const userRole = {
admin: 'admin',
guest: 'guest',
}
const USER_API_PATHS = {
root: '/',
users$Id: '/users/:id',
}
const userValidationRules = {
MIN_PASSWORD_LENGTH: 8,
MAX_NAME_LENGTH: 20,
VALID_REFERRER_DOMAINS: ['google.com'],
}
// Good
const UserRole = {
ADMIN: 'admin',
GUEST: 'guest',
}
const UserApiPath = {
ROOT: '/',
USERS_$ID: '/users/:id',
}
const UserValidationRule = {
MIN_PASSWORD_LENGTH: 8,
MAX_NAME_LENGTH: 20,
VALID_REFERRER_DOMAINS: ['google.com'],
}
Since TypeScript is now a significant part of JavaScript development, it is worth mentioning that TypeScript has the enum
keyword for enumerations. If you decide to use TypeScript's enumerations with your team, it is customary to follow the same naming conventions.
Maps. Also known as a dictionary
data structure. This data structure is used for mapping one value to another.
A map is a very useful and frequently used data structure in any programming language. For maps in the JavaScript world, there is a specific naming convention. The name should follow the pattern aToB
, where a
serves as the key for retrieving values from the map, followed by the preposition To
, implying the mapping of two things and then B
, which is the mapped value for a
.
// Bad
const userRolesReadable = {
[UserRole.ADMIN]: 'Administrator',
[UserRole.GUEST]: 'Guest',
}
const REDIRECTING = {
'/groups': '/admin-login',
'/profile': '/login',
}
const UserPermissions = {
[UserRole.ADMIN]: [Permission.MANAGE_USERS, Permission.MANAGE_GROUPS],
[UserRole.GUEST]: [Permission.EDIT_PROFILE],
}
// Good
const userRoleToReadable = {
[UserRole.ADMIN]: 'Administrator',
[UserRole.GUEST]: 'Guest',
}
const pagePathToRedirectPath = {
'/groups': '/admin-login',
'/profile': '/login',
}
const userRoleToPermissions = {
[UserRole.ADMIN]: [Permission.MANAGE_USERS, Permission.MANAGE_GROUPS],
[UserRole.GUEST]: [Permission.EDIT_PROFILE],
}
There is a built-in class in JavaScript. The main difference from a regular object is the ability to use any data type (even an object) as a key. Usually, using the native Map
class is redundant for creating a map as a data structure. However, if you need the functionality provided by the native Map
, you can use it to create a map data structure. In most cases, a regular object will suffice.
Types & Interfaces. As mentioned earlier, TypeScript has become an integral part of JavaScript development today. In general, in most cases, types and interfaces are interchangeable. However, since there are already enough discussions about which one to choose, in this article, we will focus specifically on naming. For types and interfaces, the following naming conventions exist:
PascalCase
notation.const user: User = getUserById(id)
/ const users: User[] = getUsers()
.I
and Contract
.// Bad
type TUser = {
firstName: string
lastName: string
}
interface user {
firstName: string
lastName: string
}
interface userServiceInterface {
findByEmail: (email: string) => User
}
// Good
type User = {
firstName: string
lastName: string
}
interface User {
firstName: string
lastName: string
}
interface UserServiceContract {
findByEmail: (email: string) => User
}
// Also good
interface IUserService {
findByEmail: (email: string) => User
}
In conclusion, it is essential to remember that while naming standards are important, they are not absolute. Every project has its peculiarities, and it is crucial to define your conventions. The main thing is that they should exist, and you adhere to them to ensure consistency in the code and improve collective productivity. Also, if possible, try to delegate code style issues to tools like ESLint to simplify and enhance the development process.
Also published .