of this series of articles, we looked at more common mistakes in functions and methods composition. In this article, we will focus on writing code that will be readable by a human-like book. We will consider best practices in naming, code composition, and . These practices will prevent developers from wasting time in debugging and maintaining code. In Part 1 code optimization Table of contents Naming Destructuring assignment a. Binding pattern b. Assignment pattern Template literals for string concatenations Function parameters Optional changing instead of a promise chain Async await Conditionals 1. Naming Use meaningful and pronounceable names of variables // Bad const yyyymmdd = moment().format("YYYY/MM/DD"); // Better const currentDate = moment().format("YYYY/MM/DD"); Use the same style naming for the same type of variables or functions // Bad getProductInfo(); getFoodData(); setGroceriesUpdate(); // Better getProductInfo(); getProductData(); setProductUpdate(); In this example, we have a set of functions working with the same logical level of data. To focus on this, we need to follow one naming pattern get/setProduct… Use searchable names // Bad buildHeadlinesList(documents: Doc[], 5) {/.../} // Better const HEADLINES_COUNT = 5; buildHeadlinesList(documents, HEADLINES_COUNT); In this example, we have a magic number in arguments. But for clear code, we need to create a constant with a meaningful name. It will give an understanding of what we are doing in function. 5 2. Destructuring assignment The syntax makes it possible to assign values from arrays or properties from objects into distinct variables. With this approach, we make our code compact and reduce needless computations. destructuring assignment There are two patterns in destructuring assignments: and . Let’s take a look at both of them. binding pattern assignment pattern a. Binding pattern The binding destructuring pattern starts with the declaration keyword . Then, each individual property must either be bound to a variable or further destructured. var, let, const Binding patterns could be realized in the following ways: Simple variable assignment const product = { name: "Lemon", count: 200, country: "Portugal" }; // Bad const name = product.name; const count = product.count; const country = product.country; // Better const { name, count, country } = product; Looping variable in for...in for...of and for await...of loops var users = [ { user: "Alex", bio: { age: 35, country: "Poland" }, phone: "700-12-13" }, { user: "Mike", bio: { age: 21, country: "France" } }, ]; for (let { user, phone = "DEFAULT VALUE", bio: { age, country } } of users) { console.log(user, phone, age, country); } In this example, we are destructuring an object with nested destructuring in loop to top up all values on one level and define if the key is missed in the target object or has value (if it is default value will not be assigned). for of "DEFAULT VALUE" undefined null Function parameters interface Product { name: string, count: number, country: string, } const product: Product = { name: "Lemon", count: 200, country: "Portugal" }; // Bad function analyzeProducts(product: Product): void { const name = product.name; const count = product.count; const country = product.country; // ... } // Better function analyzeProducts(product: Product): void { const { name, count, country } = product; // ... } analyzeProducts(product); The catch binding variable try { throw new TypeError("Something went wrong"); } catch ({ name, message }) { console.log(name); // "TypeError" console.log(message); // "Something went wrong" } b. Assignment pattern In the assignment pattern, there are no keywords . Each deconstructed property gets assigned to a target in the assignment. This target can be pre-declared using , or it can be a property of another object — essentially, anything permissible on the left side of an assignment expression. var, let, const var, let, const const users = []; const objUsers = { a: "Alex", b: "Mark" }; ({ a: users[0], b: users[1] } = objUsers); // ['Alex', 'Mark'] 3. Template literals for string concatenations The template literals offer brevity, cleanliness, and support for multiline strings. // Bad const helloMessage = “Hi ” + name + “, today is “ + currentDay; // Better const helloMessage = `Hi ${name}, today is ${currentDay}`; 4. Function parameters Avoid one-letter parameters even in callbacks. // Bad array.forEach((n) => { /.../ }); // Better array.forEach((node) => { /.../ }); Use default parameters instead of short-circuiting or conditionals // Bad function createBlock(name: string, lines?: number): string { const linesAmount = lines || 8; // logic } // Better function createBlock(name: string, lines = 8): string { // logic } 5. Optional changing In cases when we need to merge some objects, update some fields, or leave the old ones if they were not updated, it will be good practice to use operator or instead of conditions. spread Object.assign() const user = { name: 'Alex', email: "user@gmail.com", organisation: "XXX", secondPhone: 56459544, country: "Poland" } const updatedUserData = { email: "newmail@gmail.com", organisation: "ZZZ", } // Bad const updatedUser = { name: updatedUserData.name || user.name, email: updatedUserData.name || user.email, organisation: updatedUserData.organisation || user.organisation, secondPhone: updatedUserData.secondPhone || user.secondPhone, country: updatedUserData.country || user.country } // Better, 1 option const updatedUser = { ...user, ...updatedUserData }; // Better, 2 option const updatedUser = Object.assign(user, updatedUserData); 6. Async await instead of the promise chain When you have a set of chained promises, it would be a good practice to use to make code readable. async await // Bad analyzeProducts(products: string[]): Promise<AnalyzedProduct[]> { return this.productService.analyze(this.buildQueryParam(products)) .then((products: Product[]) => { requestIds(products) .then((products: AnalyzedProduct[]) => mapProducts(products)); .catch((error: ) => { notifyUser(error) }) }) } // Better async analyzeProducts(products: string[]): Promise<AnalyzedProduct[]> { try { const analyzedProducts = await this.productService.analyze(this.buildQueryParam(products)); const productsWithIds = await this.productService.requestIds(products); return mapProducts(productsWithIds)); } catch(error) { notifyUser(error) } } 7. Conditionals Encapsulate conditionals // Bad if (token.category === Token.Operator && isFirstToken && !token.disabled) { // ... } // Better function showToken(token: Token, model: Model<Token>): boolean { return (token.category === Token.Operator && model.length && !token.disabled) } if (showToken(token, model)) { // ... } Avoid negative conditionals For readability, it is recommended to use affirmative conditions rather than negative ones. // Bad if (!tokenIsNotHidden(node)) { /.../ } // Better if (tokenIsHidden(node)) { /.../ } Conclusion In this article, we considered the best practices for code readability. In the next article, we will take a look at code smells in Classes, Objects, and Data Structures and the ways it could improved.