A good naming convention consists of 2 parts:
- Rules of notation
- Glossary
Rules of notation mostly focuses on the shape of identifiers when defining variables, classes, methods etc. It varies greatly between languages and also between teams. The idea is to transfer additional information via shape to reader, making reading code faster.
In javascript one of the most common naming notation set is:
-
Variable and function names written as camelCase
let numberCounter = 0;
-
Global constants written in UPPERCASE
const DEFAULT_VALUE = 'none';
-
Classes and functions that are intended to be used as prototypes for instances are written in Pascal case. This leaves room for same string variable.
Class Person { name: string; constructor(name: string) { this.name = name; } }; const preson = new Pesron('John'); const Color(red:number, green: number, blue: number){ this.red = red; this.green = green; this.blue = blue; }; let color = new Color(100, 100, 100);
For CSS classes there are many conventions used, but one very popular is BEM and BEM like extended syntaxes.
General BEM spec: https://en.bem.info/methodology/quick-start/
Example of an extended syntax in a library (prefixed with library namespace dgc__
):
/** Top level element:
*
* Schema:** `.dgc__{element}`
*/
.dgc__dropdown-list {}
/** Child elements
*
* Schema:** `.dgc__{element}__{subelement}`
*/
.dgc__dropdown-list__row {}
/** Status effects. Things like `active`, `selected`, `open`
*
* Schema:** `.dgc__{element}_{status}`
*/
.dgc__dropdown-list__row_selected {}
.dgc__collapsible-window_open {}
/** Style overlay / modifier
*
* Schema:** `.dgc__{ element }--{styleModifier}`
*/
.dgc__dropdown-list--borderless {}
.dgc__primary-button--blue {}
The choice of notation convention can be a really controversial issue among developers, and each has probably his favorite and also good reasons to back it up. But keep in mind when opening an already existing codebase, it a very good idea to familiarize Yourself with the naming convention used and keep using it even if you like something else. Only consistent naming convention enjoys the benefits:
- Easier to see to what use an identifier is put (ex: you know you'll have to initialize a class to get an instance).
- Similar shapes make custom identifiers better pop out among language native code
- When namespaces are used, it avoids naming collisions among libraries, modules, product parts etc.
- Aesthetic looks - remember its art and aesthetics are important in art!
- Benefits when refactoring when using string find/replace
- Sometimes, like in the making instance from class case, it allows leaving room for same name to be used.
To simplify new developer on-boarding and avoid useless hours arguing about naming notations, it is a good idea to dedicate a section for it in projects README.md files or in company wide style-guide (or both).
To extend this common rules set and really give it some shine, I suggest adding additional (but suggested not mandatory) guidelines to naming to make reading even faster and simpler.
There is a convention to prefix boolean variables and function names with "is" or "has". The convention is not a must, but a good starting point that often improves readability.
if (isLoggedIn) {};
if (hasAccess) {};
if (isLoading()) {};
Avoid negations in boolean naming as it deeply impacts code readability. For example this takes some mind-bending to understand:
if (!account.isDisabled) {
// ...
}
const isNotGreen = true;
if (!isNotGreen) {
// ...
} else {
// ...
}
But is much more readable without double negations:
if (account.isEnabled) {
// ...
}
const isGreen = false;
if (isGreen) {
// ...
} else {
// ...
}
It is a good idea to name undefined/nullish check booleans using is{ SOMETHING }Present
schema.
const isUserPresent = Boolean(user);
In javascript world, there is a convention to prefix all your event callback methods in your module public interface
with on
. For example the method name for click
event, that others can bind their callback functions becomes
onClick
. This adds a helpful hint about your module property, and how to use it, for other developers.
<Button onClick={ handleClick }>Click me!</Button>
<Modal
title="Welcome!"
onSideClick={ handleSideClick }
>
Consider yourself welcomed!
</Modal>
There also another, not so common, convention to prefix all Your event callback handling functions with handle
. This
convention I particularly like as it, in addition to additional context benefits, also leaves room for another method
with same name but omitting the prefix. Very often a module has an action method that needs to be called from multiple
places, but event handlers have some specific event specific jargon that needs to be done in addition:
class MyComponent extents PureComponent {
constructor(props) {
this.state = {
editingValue: props.value,
isValid: this.validate(props.value)
}
}
componentDidUpdate(prevProps) {
const curValue = this.props.value;
const prevValue = prevProps.value;
const { editingValue } = this.state;
// We let user edit the input freely unless there are outside changes.
// Then those outside changes will be forced upon
if (
curValue !== prevValue &&
editingValue !== curValue
) {
this.changeEditingValue(curValue);
}
}
handleSubmitClick = () => {
const { onChange } = this.props;
const { editingValue } = this.state;
onChange(editingValue);
}
// Event specific jargon here
handleChangeEditingValue = (event) => {
const value = event.target.value;
this.changeEditingValue(value);
}
// More generic method that can be called anywhere in module
changeEditingValue = (value) => {
this.setState({
editingValue: value,
isValid: this.validate(value)
});
}
validate = (value) => {
// validate value here and return boolean
}
render() {
const { editingValue, isValid } = this.state;
return (
<div data-is-valid={ isValid }>
<input
value={ editingValue }
onChange={ this.handleChangeEditingValue }
/>
<button onClick={ this.handleSubmitClick }>Submit</button>
</div>
);
}
}
This was a quite common practice back in the day, when there was way to create private methods, to prefix
private properties with _
. But even now from time to time you want to mark an instance property, that can not be
hidden, as "for internal use", so other developers would know not to bind some functionality to it.
<MyCompoent
_customModeForTesting={ true }
/>
test('Test same message double sending block', () => {
const messenger = new Messenger();
for (let i = 0; i < 10; i++) {
messenger.sendMessage('Foo bar');
}
expect(messenger._blockedMessagesCount).toBe(9);
});
Glossary is often forgotten when talking about naming conventions but it is one of the places that can really make a
huge difference in codebase. Probably everybody has been at least once been hit with code, that does not make sense,
but somehow magically works. Then, after hours of debugging, finding that the name attribute
(or whatever the name
at hand) means really very different things in different but rather close cases by function.
From my own practice we had exactly this really bad attribute
over-use case, where eventually it might refer to:
- Id of a column in database.
- Object of a column in that database.
- A value id for a column in that database.
- Value object or translation.
- Occasionally even some other property not related to database columns and values at all.
We started fixing it by dropping the name "attribute" all-together and establishing a glossary:
columnId
- string - id of column in database.valueId
- a value id for a column in that database.
This also left some room for identifiers "column" and "value" to be used more freely inside function scope for whatever type needed at the moment.
The biggest benefit though can be gained when establishing a project-wide glossary and giving some names single fixed definitions. It helps not only in code read-ability, but in all communication, fixing some misunderstandings in tasks, making documentations easier to read, etc. The mayor hurdle though is keeping the glossary in an easily accessible place, up to date and and enforcing not only developers, but also project managers and analysts, to re-read it from time-to-time and use it in all communication.
Coders spend quite an enormous chunk of their time finding just the right name for identifiers. In some cases more time is spent on finding the names than actually typing. Thus establishing a glossary can also save a lot of time by taking some guesswork and doubt that the chosen name actually fits, out of the equation.