Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
November 24, 2021 01:17 am GMT

Writing Clean JavaScript ES6 Edition

Clean code is not just code that works, but rather code that can be easily read, reused, and refactored by others. Writing clean code is important because, in a typical work environment, you are not writing for yourself or for the machine. In reality, you are writing for a group of developers who will need to understand, edit and build over your work.

This article focuses on writing cleanJavaScript ES6code which isnot framework-specific, nonetheless, most of the mentioned examples can apply toalmost any other programming language. In general, the following concepts are recommendations mostly adopted from Robert C. Martin's bookClean Code,and they are not meant to be strictly followed.

1. Variables

Use meaningful names

Names of variables should be descriptive. The rule of thumb is that most JavaScript variables are in Camel Case (camelCase).

// Don't const foo = "[email protected]";const bar = "John";const age = 23;const qux = true;// Do const email = "[email protected]";const firstName = "John";const age = 23;const isActive = true

Note that boolean names usually answer specific questions, for example:

isActivedidSubscribehasLinkedAccount

Avoid adding unnecessary contexts

Do not add redundant context to variable names when the context is already provided by the containing object or class.

// Don't const user = {  userId: "296e2589-7b33-400a-b762-007b730c8e6d",  userEmail: "[email protected]",  userFirstName: "John",  userLastName: "Doe",  userAge: 23,};user.userId;// Do const user = {  id: "296e2589-7b33-400a-b762-007b730c8e6d",  email: "[email protected]",  firstName: "John",  lastName: "Doe",  age: 23,};user.id;

Avoid hardcoded values

Instead of plugging in constant values, make sure to declare meaningful and searchable constants. Notice that global constants can be stylized in Screaming Snake Case (SCREAMING_SNAKE_CASE).

// Don't setTimeout(clearSessionData, 900000);// Do const SESSION_DURATION_MS = 15 * 60 * 1000;setTimeout(clearSessionData, SESSION_DURATION_MS);

2. Functions

Use descriptive names

Function names can be lengthy, as long as they portray what the function actually does. Function names usually have the form of action verbs, with the possible exception of functions that return booleans --- which can have the form of a "Yes or No" question. Function names should also be in Camel Case.

// Don't function toggle() {  // ...}function agreed(user) {  // ...}// Do function toggleThemeSwitcher() {  // ...}function didAgreeToAllTerms(user) {  // ...}

Use default arguments

Default arguments are cleaner than short-circuiting or using extra conditional statements inside the function body. Though, it is important here to remember that short-circuiting works for all values that are considered "falsy" such asfalse,null,undefined,'',"",0, andNaN, while default arguments only replaceundefined.

// Don't function printAllFilesInDirectory(dir) {  const directory = dir || "./";  //   ...}// Do function printAllFilesInDirectory(dir = "./") {  // ...}

Limit the number of arguments

As controversial as this rule might be, functions should have 0, 1, or 2 arguments. Having three arguments is already excessive, and beyond that implies either of two cases:

  • The function is already doing a lot and should be divided.
  • The data passed to the function is somehow related and can be passed as a dedicated data structure.
// Don't function sendPushNotification(title, message, image, isSilent, delayMs) {  // ...}sendPushNotification("New Message", "...", "http://...", false, 1000);// Do function sendPushNotification({ title, message, image, isSilent, delayMs }) {  // ...}const notificationConfig = {  title: "New Message",  message: "...",  image: "http://...",  isSilent: false,  delayMs: 1000,};sendPushNotification(notificationConfig);

Avoid executing multiple actions in a function

A function should do one thing at a time. This rule helps reduce the function's size and complexity, which results in easier testing, debugging, and refactoring. The number of lines in a function is a strong indicator that should raise a flag on whether the function is doing many actions. Generally, try aiming for something less than 20--30 lines of code.

// Don't function pingUsers(users) {  users.forEach((user) => {    const userRecord = database.lookup(user);    if (!userRecord.isActive()) {      ping(user);    }  });}// Do function pingInactiveUsers(users) {  users.filter(!isUserActive).forEach(ping);}function isUserActive(user) {  const userRecord = database.lookup(user);  return userRecord.isActive();}

Avoid using flags as arguments

A flag in one of the arguments effectively means the function can still be simplified.

// Don't function createFile(name, isPublic) {  if (isPublic) {    fs.create(`./public/${name}`);  } else {    fs.create(name);  }}// Do function createFile(name) {  fs.create(name);}function createPublicFile(name) {  createFile(`./public/${name}`);}

Do not repeat yourself (DRY)

Duplicate code is never a good sign. If you repeat yourself you will have to update multiple places whenever there is a change in logic.

// Don't function renderCarsList(cars) {  cars.forEach((car) => {    const price = car.getPrice();    const make = car.getMake();    const brand = car.getBrand();    const nbOfDoors = car.getNbOfDoors();    render({ price, make, brand, nbOfDoors });  });}function renderMotorcyclesList(motorcycles) {  motorcycles.forEach((motorcycle) => {    const price = motorcycle.getPrice();    const make = motorcycle.getMake();    const brand = motorcycle.getBrand();    const seatHeight = motorcycle.getSeatHeight();    render({ price, make, brand, seatHeight });  });}// Do function renderVehiclesList(vehicles) {  vehicles.forEach((vehicle) => {    const price = vehicle.getPrice();    const make = vehicle.getMake();    const brand = vehicle.getBrand();    const data = { price, make, brand };    switch (vehicle.type) {      case "car":        data.nbOfDoors = vehicle.getNbOfDoors();        break;      case "motorcycle":        data.seatHeight = vehicle.getSeatHeight();        break;    }    render(data);  });}

Avoid side effects

In JavaScript, you should favor functional over imperative patterns. In other words, keep functions pure unless needed otherwise. Side effects can modify shared states and resources, resulting in undesired behaviors. All side effects should be centralized; if you need to mutate a global value or modify a file, dedicate one and only one service for that.

// Don't let date = "21-8-2021";function splitIntoDayMonthYear() {  date = date.split("-");}splitIntoDayMonthYear();// Another function could be expecting date as a stringconsole.log(date); // ['21', '8', '2021'];// Do function splitIntoDayMonthYear(date) {  return date.split("-");}const date = "21-8-2021";const newDate = splitIntoDayMonthYear(date);// Original vlaue is intactconsole.log(date); // '21-8-2021';console.log(newDate); // ['21', '8', '2021'];

Moreover, if a mutable value is passed to a function, you should return a new mutated clone of the value rather than mutating the value directly and returning it.

// Don't function enrollStudentInCourse(course, student) {  course.push({ student, enrollmentDate: Date.now() });}// Do function enrollStudentInCourse(course, student) {  return [...course, { student, enrollmentDate: Date.now() }];}

3. Conditionals

Use non-negative conditionals

// Don't function isUserNotVerified(user) {  // ...}if (!isUserNotVerified(user)) {  // ...}// Do function isUserVerified(user) {  // ...}if (isUserVerified(user)) {  // ...}

Use shorthands whenever possible

// Don't if (isActive === true) {  // ...}if (firstName !== "" && firstName !== null && firstName !== undefined) {  // ...}const isUserEligible = user.isVerified() && user.didSubscribe() ? true : false;// Do if (isActive) {  // ...}if (!!firstName) {  // ...}const isUserEligible = user.isVerified() && user.didSubscribe();

Avoid branching and return soon

Returning early will make your code linear, more readable, and less complex.

// Don't function addUserService(db, user) {  if (!db) {    if (!db.isConnected()) {      if (!user) {        return db.insert("users", user);      } else {        throw new Error("No user");      }    } else {      throw new Error("No database connection");    }  } else {    throw new Error("No database");  }}// Do function addUserService(db, user) {  if (!db) throw new Error("No database");  if (!db.isConnected()) throw new Error("No database connection");  if (!user) throw new Error("No user");  return db.insert("users", user);}

Favor object literals or maps over switch statements

Whenever this applies, indexing using objects or maps will reduce code and improve performance.

// Don't const getColorByStatus = (status) => {  switch (status) {    case "success":      return "green";    case "failure":      return "red";    case "warning":      return "yellow";    case "loading":    default:      return "blue";  }};// Do const statusColors = {  success: "green",  failure: "red",  warning: "yellow",  loading: "blue",};const getColorByStatus = (status) => statusColors[status] || "blue";

Use optional chaining and nullish coalescing

const user = {  email: "[email protected]",  billing: {    iban: "...",    swift: "...",    address: {      street: "Some Street Name",      state: "CA",    },  },};// Don't const email = (user && user.email) || "N/A";const street =  (user &&    user.billing &&    user.billing.address &&    user.billing.address.street) ||  "N/A";const state =  (user &&    user.billing &&    user.billing.address &&    user.billing.address.state) ||  "N/A";// Do const email = user?.email ?? "N/A";const street = user?.billing?.address?.street ?? "N/A";const state = user?.billing?.address?.state ?? "N/A";

4. Concurrency

Avoid callbacks

Callbacksare messy and result in nested code. ES6 offersPromiseswhich allow for chaining callbacks and thus result in cleaner code. Yet, ES6 also provides the "Async/Await" syntax as an arguably cleaner solution that imposes further linearity to code.

// Don't getUser(function (err, user) {  getProfile(user, function (err, profile) {    getAccount(profile, function (err, account) {      getReports(account, function (err, reports) {        sendStatistics(reports, function (err) {          console.error(err);        });      });    });  });});// Do getUser()  .then(getProfile)  .then(getAccount)  .then(getReports)  .then(sendStatistics)  .catch((err) => console.error(err));// or using Async/Await async function sendUserStatistics() {  try {    const user = await getUser();    const profile = await getProfile(user);    const account = await getAccount(profile);    const reports = await getReports(account);    return sendStatistics(reports);  } catch (e) {    console.error(err);  }}

5. Error Handling

Handle thrown errors and rejected promises

No need to mention why this is an extremely important rule. Spending time now on handling errors correctly will reduce the likelihood of having to hunt down bugs later, especially when your code reaches production.

// Don't try {  // Possible erronous code} catch (e) {  console.log(e);}// Do try {  // Possible erronous code} catch (e) {  // Follow the most applicable (or all):  // 1- More suitable than console.log  console.error(e);  // 2- Notify user if applicable  alertUserOfError(e);  // 3- Report to server  reportErrorToServer(e);  // 4- Use a custom error handler  throw new CustomError(e);}

6. Comments

Only comment business logic

Readable code saves you from over-commenting your code. Hence, you should only comment on complex logic.

// Don't function generateHash(str) {  // Hash variable  let hash = 0;  // Get the length of the string  let length = str.length;  // If the string is empty return  if (!length) {    return hash;  }  // Loop through every character in the string  for (let i = 0; i < length; i++) {    // Get character code.    const char = str.charCodeAt(i);    // Make the hash    hash = (hash << 5) - hash + char;    // Convert to 32-bit integer    hash &= hash;  }}// Do function generateHash(str) {  let hash = 0;  let length = str.length;  if (!length) {    return hash;  }  for (let i = 0; i < length; i++) {    const char = str.charCodeAt(i);    hash = (hash << 5) - hash + char;    hash = hash & hash; // Convert to 32bit integer  }  return hash;}

Make use of version control

There is absolutely no reason to keep commented code or journal comments, version control is already there to handle this.

// Don't /** * 2021-7-21: Fixed corner case * 2021-7-15: Improved performance * 2021-7-10: Handled mutliple user types */function generateCanonicalLink(user) {  // const session = getUserSession(user)  const session = user.getSession();  // ...}// Do function generateCanonicalLink(user) {  const session = user.getSession();  // ...}

Document when possible

Documentation helps increase code quality and reliability. It serves as a user manual for your codebase in which anyone understands all the aspects of your code.

/**   * Returns x raised to the n-th power.   *   * @param {number} x The number to raise.   * @param {number} n The power, should be a natural number.   * @return {number} x raised to the n-th power.   */ function pow(x, n) {       // ...}

This article discussed briefly some important steps that can be taken to increase your JavaScript code using the latestECMA2015/ES6syntax. Again, most of these concepts can be generalized and applied to different programming languages. Adopting these practices can take some time especially for larger codebases, but will guarantee that --- for the long run --- your code becomes readable, scalable, and easy to refactor.


Original Link: https://dev.to/allaboutishaan/writing-clean-javascript-es6-edition-5eci

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To