Categories
JavaScript Best Practices

JavaScript Best Practices for Writing More Robust Code

JavaScript is an easy programming language to learn. It’s easy to write programs that run and do something. However, it’s hard to account for all the use cases and write robust JavaScript code.

In this article, we’ll look at how to check for device types and status to make sure that our apps degrade gracefully.


Device Type Check

We should check the type of the device to see what features should be enabled. This is important since web apps don’t only work on desktops and laptops anymore.

Now we have to worry about mobile devices with various operating systems.

We can check the device type from the browser’s user agent. The global navigator object has the userAgent property that has the user agent, which has the operating system information as part of the user agent string.

For instance, we can use it as follows:

const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)

In the code above, we check for all the possible substrings in the user agent string for mobile operating systems. We mostly care about Android, iPad, and iPhone, but testing the other ones isn’t that hard.

It’s easy to spoof the user agent string to make our browser pretend to be something else. However, most people don’t do it and there aren’t many reasons for other people to do it, so it’s still a good way to check if a device is a mobile device and act accordingly.


Checking for Network Connection Status

Like checking for user agents, checking for the network connection status is important since lots of devices can go offline at any time.

Mobile devices move between fast and slow networks or to a place without cell phone signal reception. As such, we have to check for that so our apps fail gracefully when a device is offline.

Most browsers now support the Network Information API, so we can use it to check for network status easily.

We can check for the network connection type as follows:

In the code above, we just retrieve the connection type with the connection, mozConnection, or webkitConnection properties to get the initial connection object.

Then we use the effectType property from the connection object to get the effective network type.

Next, we add a change event listener to the connection object to watch for connection type changes.

As we can see, we don’t have to do much to watch for internet connection type changes. Therefore, we should definitely do this.

If we change the network connection type from the Network tab in Chrome to simulate network connection type changes or change network connection types in real life, we should then see console log outputs like this:

4g to 2g  
2g to 4g  
4g to 4g

Checking for Feature Availability

Different features may be available for different browsers by default. We should check if the features we want are supported in the browser before we implement any feature.

We can check if a feature is supported by a browser with Can I Use before using a library that might not be supported.

There’s also the Modernizr library to detect features that are available within the given browser.

We can install Modernizr by adding a script tag:

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js"></script>

It comes with classes that we can use to toggle HTML elements on and off depending on whether something is available.

For instance, it comes with the cssgradients and no-cssgradients classes to let us add different styles for situations where CSS gradients are available and not available, respectively.

With Modernizr installed, we can do the following in our CSS code:

In the code above, we have a fallback image for situations where the CSS gradients feature isn’t available, as was the case inside the .no-cssgradients header selector.

And we have our normal CSS gradients in the .cssgradients header selector.

Then we can add our own tests for features like checking if jQuery is available after adding the script tag above:

window.Modernizr.addTest("hasJquery", "jQuery" in window);

Conclusion

We should check for the device type by using the user agent. Even though it’s easily spoofable, most people won’t do it because it doesn’t provide much benefit.

Also, we should check for the connection status of a device by using the Network Information API that’s supported by many modern browsers.

Finally, we should check if a specific library or feature is available with Modernizr and Can I Use before using it.

Categories
JavaScript Best Practices

JavaScript Best Practices — Error Handling Techniques

JavaScript is a very forgiving language. It’s easy to write code that runs but has issues in it.

In this article, we’ll look at how to handle errors in JavaScript programs.

Error-Handling Techniques

Error handling is something that we got to do.

If we let them slide, then our programs crash, see corrupted, and other bad things and users won’t be happy.

Therefore, we got to handle errors properly so that users won’t see nasty things happen.

Return a Neutral Value

Returning some error values that may be harmless is one way to go.

We may want to return some neutral value that we know to be benign when an error is encountered.

If we just want to stop the code from proceeding, then we can just return something and stop the function from running.

Substitute the Next Piece of Valid Data

We can also substitute error values with some valid data we get later.

For instance, if we’re measuring temperature periodically, then we can just wait for the next reading if the previous reading failed to get the temperature.

Return the Same Answer as the Previous Time

If we encounter an error once, we can just return the same answer as before if it’s valid.

If a water meter fails to measure water usage once, we can just wait for the next reading for the new value and just return the valid value that we have now.

Substitute the Closest Legal Value

We can also substitute the closet legal value.

If our water meter can only read a volume from 0 and up and we get a negative, then we substitute with 0.

Log a Warning Message to a File

Also, we can put an entry in the program’s log for the error that we encountered.

This can be used with other techniques outlined previously.

It helps us troubleshoot what’s later on if the error persists.

Return an Error Code

Returning an error code is another valid option for handling errors.

We can return an error code to let users and other parts of our program know that an error is encountered.

To return errors, we can set the value of a status variable, we can return the status code, or we can throw an exception with the status as the value.

Call an Error-Processing Function

If our code encounters an error, we can call an error processing function to handle the error gracefully.

They would log the error and log in so that we can debug gracefully.

This is also good because we can reuse it in other parts of our system.

Display an Error Message wherever the Error is Encountered

Errors can be displayed as they’re encountered if the error can be passed into the UI component of our system.

For instance, there’re many ways to display errors in client-side JavaScript like alert , dialog boxes, and things of that sort.

So we can like something like the following:

const dispenseBeer = (age) => {  
  if (age < 21) {  
    alert('you are too young');  
  }  
  //...  
}

to display an error alert toa user.

Handle the Error in Whatever Way Works Best Locally

Errors can also be handled locally.

This carries great flexibility, but the performance of our system may suffer if we have lots of code to handle errors locally.

We also don’t want to spread that code everywhere to reduce the chance of code duplication.

Shut Down

Another thing we can do is to end our program when we encounter an error.

It’s not sure if we want to do this often as users may be surprised to see our program close when an error is encountered.

If we do this, we should make sure that pleasant user experience is maintained.

Robustness vs. Correctness

Robustness means that we do whatever it takes to keep our software operating.

Correctness means that we may sure everything is right before our program continues to run.

Consumer-facing apps favor robustness over correctness. If we’re writing JavaScript apps, it’s more likely than not that they aren’t safety-critical applications, so robustness is favored over correctness.

Conclusion

We can handle errors in many ways in our programs.

We can just take non-error values that were produced previously or wait for the next non-error value.

Also, we can throw exceptions, end our program, or display an error.

We got to think about the options that are most suitable for us.

Categories
JavaScript Best Practices

JavaScript Best Practices — Defensive Programming

JavaScript is a very forgiving language. It’s easy to write code that runs even if it has issues in it.

In this article, we’ll look at how to program defensively so that errors are handled gracefully.

Protecting Our Program from Invalid Inputs

We got to check our program for invalid inputs so that they won’t propagate in our systems.

For instance, we got to check for things like null , undefined , or invalid values all overt our code.

Also, we got to be aware of common security issues like SQL injection or cross-site scripting.

To do that, we do the following things in our program.

Check the Values of All Data from External Sources

We just can’t trust users to always enter valid data and that attackers won’t use exploits in our code.

Therefore, we should check everything thoroughly so that we can make sure that no invalid data gets propagated anywhere.

For instance, we should check for number ranges if code need numbers in a certain range.

Also, we shouldn’t use things like the eval function or the Function constructor which runs code from a string.

Check the Values of All Function Input Parameters

We got to check the values before we use them in a function.

For instance, we can write a function that dispenses beer to adults only as follows:

const dispenseBeer = (age) => {
  if (age < 21) {
    return 'you are too young';
  }
  //...
}

We check age before dispensing beer so that we won’t mistakenly dispense beer to underage people.

Decide How to Handle Bad Inputs

Bad inputs have to be handled in a graceful way.

We got to check them and then find a good way to deal with them.

Returning a message or throwing exceptions are good ways to handle invalid inputs.

For instance, our dispenseBeer function can be changed to show an alert box instead:

const dispenseBeer = (age) => {
  if (age < 21) {
    alert('you are too young');
    return;
  }
  //...
}

Or we can write the following:

const dispenseBeer = (age) => {
  if (age < 21) {
    throw new Error('you are too young');
  }
  //...
}

We don’t need the return since throwing exceptions would end the execution of the function.

Assertions

Also, we can use assertions to check for inputs before we run the function.

For instance, we can write the following using the assert module as follows:

const assert = require('assert');

const dispenseBeer = (age) => {
  try {
    assert.equal(age >= 21, true);
    //...
  }
  catch {
    return 'you are too young';
  }
}

This does the same thing as throwing an error since it checks if age is bigger than 21.

Otherwise, an assertion error would be thrown and ‘you are too young’ is returned.

This is the same as throwing an exception.

There’re many other kinds of assertions we can make with the assert module.

They include:

  • checking for equality
  • checking for inequality
  • checking for exceptions thrown
  • checking for deep equality
  • checking for deep inequality
  • …and others

Guidelines for Using Assertions

There’re some guidelines for using assertions.

Use Error-Handling Code for Conditions that are Expect to Occur

Error handling code should be used for checking for conditions that might be invalid.

In this case, we should handle those cases instead of using assertions.

Things like bad input values should be checked with error handling code.

Use Assertions for Conditions that Should Never Occur

If something should never occur, then we should use assertions to stop the code from running if they’re encountered.

We can run the rest of the code assuming that condition that we asserted to never occur won’t occur.

Avoid Putting Executable Code into Assertions

We should only use assertions to only check for things.

It keeps them clean and prevents any confusion.

Use Assertions to Document and Verify Preconditions and Postconditions

Preconditions that should be verified should have an assertion.

Since it;’s in the code, then we know what the codes check for when we read the code before running the rest of the code.

When the program ends, we can check for postconditions with assertions to check for conditions we look for.

Conclusion

We should check for invalid conditions before we run anything.

To do that, we can use assertions or error handling code. Assertions are better for checking for conditions that should never occur while error handling code are better for other cases.

Categories
JavaScript Best Practices

JavaScript Best Practices: Useless Code, Comparisons, and Eval

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at some JavaScript best practices to follow, including avoiding empty function and destructuring, null comparison, avoiding useless bind and avoid eval.


Don’t Write Empty Functions

Empty functions aren’t useful and we don’t know if they’re intentional or not. This means that we should at least write a comment to let people know whether it’s intentional.

This also goes for arrow function callbacks that return nothing, since they look similar to an empty object.

For instance, we should avoid:

function foo() {}
arr.map(() => {});
arr.map(() => ({}));

Instead, we should write:

function foo() {
  // do nothing
}

or:

arr.map(() => {}); // returns nothing

or:

arr.map(() => ({})); // returns empty object

Empty Destructuring Patterns

Empty destructuring patterns are also useless, so there’s no point in creating them. For instance, the following isn’t useful:

const {a: {}} = foo;

It might also be mistaken for setting a to an empty object as the default value as follows:

const {a = {}} = foo;

We should avoid having empty destructuring patterns in our code. If we have any, we can and should remove them.


Null Comparisons with ==

null comparisons with == or != also compares the operand we’re comparing with other values with undefined . Therefore, the following are also true if we compare foo with null in the following code:

let foo = undefined;
foo == null;

We get that foo == null returns true, which is probably not what we want. Likewise, if we have the following comparison with !=:

let foo = undefined;
foo != null;

We get that foo != null is false even though foo is undefined . Therefore, we should instead use === and !== to check for null as follows:

let foo = undefined;
foo === null;

and:

let foo = undefined;
foo !== null;

Never Use eval()

eval lets us pass in a string with JavaScript code and run it. This is dangerous because it can potentially let anyone run JavaScript code that we didn’t write. It can open up our program to several kinds of injection attacks.


No Extension of Native Objects

In JavaScript, we can extend any native objects with their own methods. However, that’s not a good idea because it may break other pieces of code that use native objects in ways that we don’t expect.

For instance, if we have the following:

Object.prototype.foo = 55;

const obj = {
  a: 1,
  b: 2
};

for (const id in obj) {
  console.log(id);
}

We see that foo is logged in addition to a and b because the for...in loop iterates through all enumerable properties in the current object and the object’s prototype chain.

Since JavaScript extends the JavaScriptObject object by default, the for...in loop will also loop through enumerable properties in the Object’s prototype.

Therefore, we shouldn’t extend native objects as it may do things that we don’t expect to other code.


No Unnecessary Function Binding

The bind method is a method of a function that changes the value of this inside a traditional function. Therefore, if our function doesn’t reference this, then we don’t need to call bind on it to change the value of this.

For example, the following function uses bind with a purpose:

function getName() {
  return this.name;
}
const name = getName.bind({
  name: "foo"
});
console.log(name());

In the code above, we have the getName function, then we called bind on it to change the value of this to { name: “foo” }. Then we when we call name in the last line, we see return this.name , which should be 'foo' since we changed the value of this to the object.

On the other hand, if we have:

function getName() {
  return 'foo';
}
const name = getName.bind({
  name: "foo"
});
console.log(name());

Then the call to bind is useless since getName didn’t reference this. Therefore, if our function doesn’t reference this, then we don’t need to call bind on it.


Conclusion

Comparing null with == isn’t very useful since expressions like undefined == null also return true because of type coercion. To ensure attackers can’t run malicious code, eval shouldn’t be called.

bind is useless if we don’t reference this in our function, so if we do call it, we should make sure that we have this in our function. Other things like empty functions and destructuring patterns are useless, so they should be avoided.

Categories
JavaScript Best Practices

JavaScript Best Practices — Classes and Functions

Cleaning up our JavaScript code is easy with default parameters and property shorthands.

In this article, we’ll look at the best practices when we create classes and functions.

Avoid Creating God Classes

God classes are classes that are all-knowing. If a class is mostly used for retrieving data from other classes using get and set methods, then we don’t need the class.

So if we have something like this:

class Foo {
  getBar() {}
  setBar() {}
  getBaz() {}
  setBaz() {}
  getQux() {}
  setQux() {}
}

We can remove the class, and access the things we need directly.

Eliminate Irrelevant Classes

If we need a class, then we should remove it.

This applies to classes that only have data. If we have members that are in the class, then we should consider if they should be members of another class instead.

Avoid Classes Named After Verbs

We shouldn’t name classes with verbs since a class that has only behavior but not data shouldn’t be a class.

For instance, if we have:

class GetFoo {
  //...
}

Then it should be a function instead of a class.

When Should We Create Functions?

We should create functions to make our code better. The following are the reasons for creating functions.

Reduce Complexity

Reducing complexity is one of the most important reasons for creating functions.

We got to create them so that we don’t repeat code and minimizing code size.

If we write all procedures without functions, then we would have lots of similar code.

For instance, if we have:

const greet1 = `hi joe`;
const greet2 = `hi jane`;

Then we can make a function to generalize that:

const greet = (name) => `hi ${name}`;
const greet1 = greet('joe');
const greet2 = greet('jane');

Now we can call greet anywhere to create similar strings.

Introduce an Intermediate, Understandable Abstraction

The code above is also an abstraction. We generalized the code for returning greeting strings.

We can pass in different values of name and return a new string.

Avoid Duplicate Code

Avoiding duplicate code is also a good reason for creating functions.

Again, as we can see from the example above, we can call greet to create many more of the same strings.

Then we don’t have to repeat the hi part everywhere.

Support Subclassing

We can create a method in a subclass to override a method in the superclass.

For instance, we can write the following code to do that in JavaScript:

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name} speaks`;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }
  speak() {
    return `${super.speak(this.name)} in dog language`;
  }
}

We have overridden the speak method in Dog by calling super.speak .

Therefore, we can keep the speak method in Dog simpler.

Photo by Danielle Cerullo on Unsplash

Hide Sequences

Functions are good for hiding implementations of procedures.

For instance, we can have a function that calls other functions and do something with them.

We can write:

const calcWeight = () => {
  //...
}

const calcHeight = () => {
  //...
}

const calcWidth = () => {
  //...
}

const getAllDimensions = () => {
  const weight = calcWeight();
  //...
  const height = calcHeight();
  //...
  const width = calcWidth();
  //...
}

We called multiple functions in the code above. But other parts of our program won’t know the order that they’re called.

This reduces the implementation that is exposed and reduces coupling.

Improve Portability

Functions can be moved anywhere easily. We can move it and anything else that depends on it easily.

Simplify Complicated Boolean Tests

If we have long boolean expressions, then we should put them into their own function.

For instance, if we have:

const isWindowsIE = navigator.userAgent.toLowerCase().includes('windows') &&
  navigator.userAgent.toLowerCase().includes('ie');

Then we can put the whole thing into a function by writing:

const isWindowsIE = () => {
  const userAgent = navigator.userAgent.toLowerCase()
  return userAgent.includes('windows') &&
    userAgent.includes('ie');
}

It’s just much cleaner than having long boolean expressions.

Conclusion

God classes, classes that only data or behavior are all bad. We should make sure that we have a mix before creating both.

We create functions to reduce complexity, remove duplicate code, and make abstractions.

They’re good for creating anything.