Categories
JavaScript Best Practices

Maintainable JavaScript — Objects We Don’t Own

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at how to handle objects we don’t own.

Objects We Own

The objects we own are the ones that we create.

The code that creates the object may not be written by us, but the object is created by us.

This means the objects we don’t own our ones like native objects such as Object , Date , etc.

We also don’t own DOM objects, built-in global objects, or library objects.

These are all part of the project’s environment.

These should be treated as read-only.

We shouldn’t override methods to these objects.

And we shouldn’t add or remove existing methods from these objects.

This is because it’s easy for us to do things that no one else expects.

This leads to confusion and waste time tracing unexpected code.

Don’t Override Methods

Overriding methods of objects we don’t own is one of the worst practices in JavaScript.

Within scripts, it’s very easy to override an existing methods.

For instance, we can write:

document.getElementById = () => {
  return null;
}

Then everyone would be confused of why document.getElementById is always returning null .

In a script, there’s nothing preventing us from overwriting DOM methods.

We can also overwrite any other property in any library code.

For instance, someone may write code like:

document._originalGetElementById = document.getElementById;
document.getElementById = (id) => {
  if (id === "window") {
    return window;
  } else {
    return document._originalGetElementById(id);
  }
};

This is also bad since it changes the way that standard library methods work.

This also brings confusion just like anything else.

The new code is called with id is 'window' , but the original is used with anything else.

This is just as bad since getElementById sometimes works as we expect and sometimes does.

Therefore, we should never override any methods so that we always have them work like expect them to.

Don’t Add New Methods

It’s also pretty easy to add new methods to existing objects in JavaScript.

We only need to assign a function onto an existing object to make it a method.

It allows us to modify all kinds of objects.

For instance, we can add methods to document :

document.foo = () => {
  console.log('hello');
};

We can also add methods to the Array ‘s prototytpe :

Array.prototype.foo = () => {
  console.log('hello');
};

They’re both bad.

And there’s nothing stopping us from adding methods to them.

Like with overriding methods, we make a library object behave differently from what we expect.

Also, the method that we added may be added to the standard library.

Then we have 2 methods that do different things and we overwrote it with our version.

Even subtle differences can cause lots of confusion.

Conclusion

We shouldn’t add or override methods of any built-in or library objects.

This is because we’ll be confused on why the code works differently than we expect.

Categories
JavaScript Best Practices

Maintainable JavaScript — Handling Errors

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at ways to handle errors.

Advantages of Throwing Errors

Throwing our own errors allow us to provide the exact text to be displayed by the browser.

We can include any information we like in addition to the line and column numbers.

The more information is given, the easier it’ll be for us to find the issue.

For instance, if we have:

function getDivs(element) {
  if (element && element.getElementsByTagName) {
    return element.getElementsByTagName("div");
  } else {
    throw new Error("divs not found");
  }
}

Then we throw an error is there’re no divs found in the element.

Knowing this makes the problem easier to find.

When to Throw Errors

Throwing errors everywhere is impractical and decrease our app’s performance.

For instance, if we have:

function addClass(element, className) {
  if (!element || typeof element.className !== "string") {
    throw new Error("element class name must be a string");
  }
  if (typeof className !== "string") {
    throw new Error("class name must be a string");
  }
  element.className += ` ${className}`;
}

then most of the function is taken up by error throwing code.

This is overkill in JavaScript.

We should only throw errors for code that’s the most likely to throw errors.

So we don’t need to throw error for the className parameter.

Instead, we write:

function addClass(element, className) {
  if (!element || typeof element.className !== "string") {
    throw new Error("element class name must be a string");
  }
  element.className += ` ${className}`;
}

element.className is most likely not to be a string, so we check for that.

Error checking isn’t necessary for known entities.

If we can’t identify the places where the function will be called ahead of time, then it’s a good idea to throw errors if something is unexpected.

JavaScript libraries should throw errors from their public interfaces for known error conditions.

This way, programmers can know where the issue is if they used it incorrectly.

The call stack should terminate in the library’s interface.

This also applies to private libraries.

Once we fixed hard to debug errors, we should throw some errors that let us identify the problem easier.

If we’re writing code that may mess up something if it does something unexpected, then we should check for those conditions and throw errors.

If the code is going to be used by someone we don’t know, then the code’s public interface should throw errors for things that are unexpected.

The try-catch Statement

JavaScript provides us with the try-catch statement to let us intercept thrown errors before they’re handled by the browser.

The code that can cause an error is in the try block and the error handling code is in the catch block.

For instance, we can write:

try {
  errorFunc();
} catch (ex) {
  handleError(ex);
}

We can also add a finally clause below the catch block.

The finally block has code that’s run regardless of whether an error occurs.

For instance, we can write:

try {
  errorFunc();
} catch (ex) {
  handleError(ex);
} finally {
  cleanUp();
}

The finally clause can be tricky.

If the try clause has a return statement, then it won’t return until finally has run.

Conclusion

We should only throw errors at the interface level and for errors that occur most often.

Also, we can use the try-catch-finally clauses to handle errors.

Categories
JavaScript Best Practices

Maintainable JavaScript — Facade, and Object Freezing

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at some design patterns.

Facade Pattern

The facade pattern is a design pattern that lets us create a new interface for existing objects.

Facades are also called wrappers because they wrap existing objects with a different interface.

For instance, we can create a class with methods to manipulate DOM objects.

We can write:

class DOMWrapper {
  constructor(element) {
    this.element = element;
  }

  addClass(className) {
    element.className += ` ${className}`;
  }
}

We have a constructor that takes an element object which is a DOM element.

Then we have an addClass method to let us add the className to the className string property.

Then we can use it by writing:

const wrapper = new DOMWrapper(document.querySelector("div"));
wrapper.addClass("selected");

A wrapper class is useful for extending the functionality of existing objects.

We have complete control over the interface so that we can control the functionality that’s available to the user.

Also, we can add new methods that are simpler to use than existing ones.

Polyfills

Polyfills becomes popular when ES5 + and HTML5 features are being added to browsers.

We can use polyfills to make features available to our apps before they’re added to browsers.

Many methods of built-in objects like forEach , map , filter , etc. of arrays are added after the first version of JavaScript.

This may not be available in older browsers, so we have to add polyfills for ones that don’t.

Once the browsers our app supports implemented the features, then we no longer need the polyfill.

We can easily remove them when we only support browsers with native functionality.

When we pick a polyfill, we should make sure it matches the native version as much as possible.

Preventing Modification

ES5 added some methods to prevent the modification of objects.

They’re supported in almost all modern browsers.

We can prevent the adding properties to an object by using the Object.preventExtensions method.

The Object.seal method has the capabilities of preventExtensions . And it prevents existing properties and methods from being deleted.

The Object.freeze method has the capabilities of seal , and it prevents existing properties and methods from being modified.

So we can write:

const person = {
  name: "james"
};
Object.preventExtensions(person);

Then we can call the Object.isExtensible method to check if the object is frozen or not:

console.log(Object.isExtensible(person));

If it’s false , then it’s frozen.

And if we try to add a property to it:

person.age = 50;

it’ll fail silently unless the code has strict mode enabled.

Likely, we can call Objec.seal() to seal an object.

Then we can write:

const person = {
  name: "james"
};

Object.seal(person);
console.log(Object.isSealed(person));

We sealed the object, so isSealed should return true .

Now if we try to add or remove properties:

person.age = 50;
delete person.name;

they’ll both fail silently unless strict mode is on.

Likewise, we can use the Object.freeze method to freeze an object.

We can write:

const person = {
  name: "james"
};

Object.freeze(person);

Then we can check if we can modify our object by writing:

console.log(Object.isExtensible(person));
console.log(Object.isSealed(person));
console.log(Object.isFrozen(person));

person.name = "mary";
person.age = 25;
delete person.name;

isExtensible should return false and the other 2 should be true .

The object modifying code should fail silently if strict mode is off.

And if strict mode is on, then we should see error messages.

Conclusion

There’re various Object static methods to stop us from changing our objects.

Also, we can create a facade around our existing code to create a new interface for them.

Categories
JavaScript Best Practices

Maintainable JavaScript — Error Class and Checks

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at the best ways to handle errors.

try-catch

We should never have try-catch statements with an empty block.

They should be handled in some way so that our code won’t fail silently.

For instance, we don’t write:

try {  
  errorFunc();  
} catch (ex) {  
  // do nothing  
}

We don’t want to ignore errors.

Error Types

The ES specification specifies several kinds of built-in errors type.

Error is the base constructor for all errors.

EvalError throws when an error occurs during execution of eval .

RangeError is throw when a number is outside the bounds of its range.

They rarely occur during normal execution.

ReferenceError is thrown when an object is expected but it’s not available.

SyntaxError is thrown when code has a syntax error.

TypeError is thrown when a variable is of an unexpected type.

URIError is thrown when the URI we passed into encodeURI , encodeURIComponent , decodeURI , or decodeURIComponrnt is incorrectly formatted.

If we understand the different types of errors, then it’s easy to handle them.

All error types inherit from error.

And if we check for specific types of errors, we get better error handling.

For instance, we can write:

try {  
  // ...  
} catch (ex) {  
  if (ex instanceof TypeError) {  
    // ...  
  } else if (ex instanceof ReferenceError) {  
    // ...  
  } else {  
    // handle all others  
  }  
}

We check which type of error is thrown with the instanceof operator, and we can write error-handling code for each kind of error.

Throwing Error instances have their advantages.

Error objects thrown are handled by browser’s error handling mechanism.

There’s also extra information in the Error object like the line and column number and stack trace.

If we want to throw custom errors, we can create our own Error class.

For instance, we can write:

class MyError extends Error {  
  constructor(message) {  
    super();  
    this.message = message;  
  }  
}

We used the extends keyword to create a subclass of Error .

Then we can use it by writing:

throw new MyError('error occurred');

Now if we throw the error, then it’ll respond like any native error.

Most modern browsers will handle them the same way so we don’t have to worry about browser compatibility.

With our own error class, we can check for the error type by writing:

try {  
  // ...  
} catch (ex) {  
  if (ex instanceof MyError) {  
    // ...  
  } else {  
    // handle all others  
  }  
}

We check the error type with instanceof like any other error.

Conclusion

We can create our own error class so that we can include the information we want in the error.

Also, we check for the type of error that’s thrown with the instanceof operator.

JavaScript has several types of built-in error types.

Categories
JavaScript Best Practices

Maintainable JavaScript — Wrappers and Coupling

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at wrapper types and loose coupling.

Primitive Wrapper Types

Primitive wrapper types are instances of the String , Boolean , and Number constructors.

They’re little known and often misunderstood pars of the JavaScript language.

They all exist as constructors in the global scope and each represents the object form of the respective primitive type.

They make primitive values act like objects.

For instance, we can write:

const name = 'james'.toUpperCase()

to return 'james' in upper case form.

What it’s done in this line is that 'james' is converted into an object with the String constructor.

Then we called the toUpperCase method in the string instance.

This is what JavaScript engines do under the surface.

After the result is returned, it’s destroyed automatically.

Another one will be created when it’s needed.

It’s possible to use these constructors in our own code.

For instance, we can write:

let name = new String("james");  
const author = new Boolean(true);  
const count = new Number(10);

We used the primitive wrapper constructors to create wrapper objects.

However, they make the code longer and more confusing, so we should avoid them.

The Google and Airbnb style guides all flag them.

And linters like ESLint and JSHint will all warn us if they exist.

Loose Coupling of UI Layers

JavaScript is often used for creating web app UIs.

We create them with the HTML is used to define the semantics of the page.

CSS is used for styling and layout of the page.

And JavaScript adds dynamic behavior to our page to make it more interactive.

CSS and JavaScript act like siblings.

It doesn’t have a dependency on CSS, but we can use it to do the styling

We can have a page with just HTML and CSS.

And it’s possible to have a page with just HTML and JavaScript.

To make web UI code maintainable, need to make the coupling of each part loose.

Tight coupling means that one component has direct knowledge of the other and if one changes, then the other would also change.

For instance, if the alert CSS class is used everywhere in our HTML, then when we need to change the alert class to something else, then we need to change it somewhere else.

Loose coupling is achieved when we can make changes to a single component without affecting the others.

We want to make code changes easy, so we need components to be loosely coupled.

However, there’s no way that 2 components have no coupling.

They’ll have to interact with each other somehow.

A loosely coupled web UI is easier to debug.

We can look at HTML to check the text or structure.

When styling issues arise, we know it’s probably in the CSS.

And if there’re any behavior issues, then we can look at the JavaScript.

Conclusion

We should keep our code loosely coupled and avoid using primitive wrapper constructors in our code.