Like any kind of apps, JavaScript apps also have to be written well.
Otherwise, we run into all kinds of issues later on.
In this article, we’ll look at why and how to avoid mutations and complexity.
Also, we look at security issues.
Don’t use Proxy
Proxy adds side effects to object operation, so we may want to avoid them to reduce complexity.
For instance, instead of writing:
const handler = {
get(target, key) {
return Math.max(target[key], Infinity);
}
};
const object = new Proxy(variable, handler);
object.a;
Instead, we create a function:
const bigProperty = (target, key) => {
return Math.min(target[key], Infinity);
}
positiveProperty(object, 'a');
Reduce the use of Rest Parameters
Rest parameters are handy because we can pass in any number of arguments into a function.
However, it’s better to have explicit parameters since they’re easier to deal with.
A function with rest parameters doesn’t work well with currying since there is an indefinite number of parameters.
Therefore, instead of using:
const sum = (...nums) => {
return nums.reduce((a, b) => a + b);
}
We write:
const sum = (nums) => {
return nums.reduce((a, b) => a + b);
}
nums
is an array in the 2nd example.
Having one fixed parameter is better than an indefinite number of arguments in an array.
Reduce the use of this
The use of this
should be reduced since the use of this
means we have a mutating internal state.
Also, this
is confusing in JavaScript.
Therefore, it’ll be a mess if we use it. The value of this
changes according to scope.
The use of throw
We shouldn’t use throw
too much. It should be reserved for issues that we can’t resolve in our app.
It also should be used for the most critical errors so that everyone can see them right away.
In other cases, we may want to return data instead of in our function.
So instead of writing:
const throwAnError = () => {
throw new Error('error');
}
We may write:
const returnAnError() {
return new Error('error');
}
No Unused Expressions
We shouldn’t have any unused expressions in our code.
So things like:
1 + 2
shouldn’t be in our code since it’s useless.
Instead, we should assign expressions to a variable or pass them in as arguments of functions to use them.
So we can write:
const sum = 1 + 2;
No valueOf Fields
valueOf
fields are useless since it just convert a value stored in an object into a primitive value.
This is a roundabout and confusing way to create primitive values.
Instead, we should just create primitive values instead.
So instead of writing:
const obj = {
value: 200,
valueOf() { return this.value; }
};
We just write:
const a = 200;
Always use new with Error
Error
is a constructor, so we should use new
with it.
In JavaScript, it’s easy to skip new
if the class syntax isn’t used.
We’ll get unexpected results if we forgot it in other places.
So to be consistent, we should use new
everywhere, including when we’re creating Error
objects.
For instance, instead of writing:
throw Error('foo');
We write:
throw new Error('foo');
Security Risks
There are security risks with things like regular expressions.
We should be careful when using them so that they don’t break our apps.
Regular Expression DoS and Node.js
Node.js has issues with big regular expressions which are checked against long strings.
These big expressions block the event loop and so our app will be stalled until the code finishes running.
Since Node.js is mostly single-threaded, we’ll have issues with big regex checks.
Regex with some attributes like grouping with repetition creates more issues than others.
So if we have an email regex like:
/^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+.)+([a-zA-Z0-9]{2,4})+$/
which has many groups, then Node.js will be slow in checking strings against that regex.
Conclusion
We shouldn’t use proxies most of the time to simplify the code.
The use of this
should always be reduced so as to reduce confusion and the chance of errors.
Also, we should check if a regex won’t stall our Node.js program before using it.