One of the most useful principle of software development which should be permanently kept in mind is not a new one. Actually, it's an almost millenial assertion: Entities must not be multiplied without necessity
, a sentence frequently associated with the Occam's razor concept. Despite his canonical age, the validity of the sentence remains intact in the sofware engineering field and it has been adaptated in many ways (the KISS principle is one among many others).
Therefore, what are the implications of such a law for a JavaScript programmer? And why is a computer language typing concept related to it?
A dispute about names and types
Every developer knows that JavaScript is not a static typed language, a useful feature eliminating lots of bugs (a language like Typescript has been created as a remedy to that important lack of safety). Even for code modules counting less than a few tens of lines, it's easy to forget what exactly is the type of a variable declared at the begining of the file and then make a mistake when assining a wrong type to that variable.
TypexJS is a simple convention designed to avoid such mistakes: by only adding a mnemonic letter at the end of each variable identifier to specify its type (of course, this scheme also applies to constants).
This simple adjonction has two main benefices:
- it gathers tightly related variables in a consistent semantic field;
- it simplifies identifier derivations.
Just an example, taken from MDN String documentation. The JavaScript String.prototype.split
method returns an array of Strings:
var str = 'The quick brown fox jumps over the lazy dog.';
var words = str.split(' ');
Two different words for two tightly related entities! Isn't it semantically more meaningful to use the same identifier with different specifiers?
const sentence_s = 'The quick brown fox jumps over the lazy dog.';
const sentence_a = sentence_s.split(' ');
A more tricky example (with a smart inline type coercion tricks!):
let number_s = '123'
let number_n = +number_s //: cast to Number
number_s = '' + ++number_n //: cast to String
Types specifiers
TypexJS naming scheme applies to all primitive immutable types:
- Null
- Undefined
- Boolean
- Number
- BigInt
- String
- Array
- Object
- Symbol
It extends to specific Object types:
- Class
- RegExp
- Function
The suffix specifying the type is a single letter preceded by an underscore character:
- lowercase letters for "wrapped" types: Boolean, Number, BigInt, String, Array, Object, Symbol, Function, Class, RegExp (see MDN Primitive description)
- uppercase letters for "unwrapped" types: Null, Undefined.
Type | Suffix |
---|---|
Null | _N |
Undefined | _U |
Boolean | _b |
Number | _n |
BigInt | _i |
String | _s |
Array | _a |
Object | _o |
Symbol | _y |
Class | _c |
RegExp | _r |
To convey even more meaningful information, function identifiers follow a slightly different scheme: two underscore characters before the type specifier of the returned value. Consequently, if a function doesn't return anything (void
function), the v letter is used for its suffix!
Function return is | Suffix |
---|---|
Null | __N |
Undefined | __U |
Boolean | __b |
Number | __n |
BigInt | __i |
String | __s |
Array | __a |
Object | __o |
Symbol | __y |
Class | __c |
RegExp | __r |
Function | __f |
Void | __v |
const awesome__s = () => 'An awesome String'
const clone__s = awesome__s
In case of a function returning different types of value, we just omit the type character:
const silly__ = string_b => string_b ? 'A weird String' : 1234
We could do the same for a variable accepting different kinds of type, but is it really a good practice?
let hybrid_ = 'A String'
hybrid_ = 1234 //: What a mess!
Full example
const DOM_siblings__a = selector_s =>
{
const node_e = document.querySelector( selector_s )
return !node_e ?
null :
Array.prototype.filter
.call( node_e.parentNode.children, sibling_e => sibling_e !== node_e )
}
const DOM_listReverse = selector_s =>
{
const node_a = Array.prototype.slice.call( document.querySelectorAll( `${selector_s} li` ))
node_a.forEach( node_e => node_e.parentNode.insertBefore( node_e, node_e.parentNode.firstChild ) )
}
The previous code shows an exception to the specifiers convention used by TypexJS: for DOM
elements, it seems more appropriate to use the _e
suffix (for element, because the _N
suffix is already used for a Null
value) than the _o
suffix used for Object
.
The example code shows another singularity: we can instantly differenciate JavaScript and browser specific functions or methods from our own code functions and methods. Our identifiers have a letter suffix where JS and browser identifiers have nothing. Therefore, we can safely use exactly the same names:
const myWindow = window // usual way
const window_o = window // smarter isn't it?
Hence, everytime we want to override some JavaScript or DOM function
(or any browser specific, third-party library function, etc.), we can safely do it while keeping a semantic coherency with the original function.
This very simple and easy to memorize convention can help you to write a better code while conveying more information about it as well as shortening its documentation. You can find more details about TypexJS visiting its dedicated site and put comments or questions if you see fit.
Top comments (0)