17. Best Practices

Code Style and Formatting

Consistent Naming Conventions

// Variables and functions: camelCase
const userName = "John";
function getUserData() { }

// Classes and constructors: PascalCase
class UserAccount { }

// Constants: UPPER_SNAKE_CASE
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = "https://api.example.com";

// Private properties/methods: prefix with underscore
class Database {
    _connectionString = "";
}

// Boolean variables: prefix with is/has/can
const isLoggedIn = true;
const hasPermission = false;
const canEdit = true;

Code Formatting

// Use consistent indentation (2 or 4 spaces)
function calculateTotal(items) {
    let total = 0;

    for (const item of items) {
        total += item.price * item.quantity;
    }

    return total;
}

// Use semicolons consistently
const a = 1;
const b = 2;

// Space around operators
const sum = a + b;
const isEqual = a === b;

// Consistent brace style
if (condition) {
    // code
} else {
    // code
}

// Line length limit (80-100 characters)
const longVariableName = someVeryLongFunctionNameThatReturnsAValue();

Variable and Function Declaration

Prefer const over let

// Good
const PI = 3.14159;
const user = { name: "John" };
const numbers = [1, 2, 3];

// Bad
let PI = 3.14159; // Should be const
let user = { name: "John" }; // Could be const

Use let only when reassignment is needed

// Good
for (let i = 0; i < 10; i++) {
    // i is reassigned in each iteration
}

// Bad
let count = 0;
count = 1; // If never reassigned, use const

Function Declaration vs Expression

// Function declarations are hoisted
function greet(name) {
    return `Hello, ${name}!`;
}

// Function expressions are not hoisted
const greet = function(name) {
    return `Hello, ${name}!`;
};

// Arrow functions for concise expressions
const greet = name => `Hello, ${name}!`;

Object and Array Practices

Object Destructuring

// Good
function processUser({ name, age, email }) {
    console.log(`${name} is ${age} years old`);
}

// Bad
function processUser(user) {
    console.log(`${user.name} is ${user.age} years old`);
}

// Default values
function createConfig({ theme = 'light', lang = 'en' } = {}) {
    return { theme, lang };
}

Array Destructuring

// Good
const [first, second, ...rest] = [1, 2, 3, 4, 5];
const { name, age } = user;

// Bad
const first = arr[0];
const second = arr[1];

Spread Operator

// Array spreading
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]

// Object spreading
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }

// Function arguments
function sum(a, b, c) {
    return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

Error Handling

Use try/catch for synchronous errors

function parseJSON(jsonString) {
    try {
        return JSON.parse(jsonString);
    } catch (error) {
        console.error('Invalid JSON:', error.message);
        throw new Error('Failed to parse JSON');
    }
}

Handle promises properly

// Good
async function fetchUser(id) {
    try {
        const response = await fetch(`/api/users/${id}`);
        if (!response.ok) {
            throw new Error(`HTTP error: ${response.status}`);
        }
        return await response.json();
    } catch (error) {
        console.error('Failed to fetch user:', error);
        throw error; // Re-throw to let caller handle
    }
}

// Bad - not handling errors
async function fetchUser(id) {
    const response = await fetch(`/api/users/${id}`);
    return await response.json();
}

Custom error classes

class ValidationError extends Error {
    constructor(message, field) {
        super(message);
        this.name = 'ValidationError';
        this.field = field;
    }
}

class NetworkError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.name = 'NetworkError';
        this.statusCode = statusCode;
    }
}

Asynchronous Code

Prefer async/await over promises

// Good
async function processData() {
    try {
        const data = await fetchData();
        const processed = await processData(data);
        return processed;
    } catch (error) {
        console.error('Processing failed:', error);
    }
}

// Less readable
function processData() {
    return fetchData()
        .then(data => processData(data))
        .then(processed => processed)
        .catch(error => console.error('Processing failed:', error));
}

Avoid callback hell

// Bad
getUser(id, (user) => {
    getPosts(user.id, (posts) => {
        getComments(posts[0].id, (comments) => {
            console.log(comments);
        });
    });
});

// Good
async function getCommentsForFirstPost(userId) {
    const user = await getUser(userId);
    const posts = await getPosts(user.id);
    const comments = await getComments(posts[0].id);
    return comments;
}

Module Organization

Use ES6 modules

// lib/math.js
export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

export default {
    add,
    multiply
};

// main.js
import { add, multiply } from './lib/math.js';
import mathUtils from './lib/math.js'; // Default import

Avoid global variables

// Bad
let globalCounter = 0;

function increment() {
    globalCounter++;
}

// Good
class Counter {
    constructor() {
        this.count = 0;
    }

    increment() {
        this.count++;
    }
}

const counter = new Counter();

Performance Considerations

Avoid unnecessary object creation

// Bad - creates new object each time
function getUserInfo(user) {
    return {
        name: user.name,
        age: user.age,
        email: user.email
    };
}

// Good - reuse object or use destructuring
function getUserInfo(user) {
    const { name, age, email } = user;
    return { name, age, email };
}

Use efficient loops

const numbers = [1, 2, 3, 4, 5];

// Good - for...of for arrays
for (const num of numbers) {
    console.log(num);
}

// Good - for...in for objects
const obj = { a: 1, b: 2 };
for (const key in obj) {
    console.log(key, obj[key]);
}

// Avoid - for...in for arrays (includes prototype properties)
for (const index in numbers) {
    console.log(numbers[index]);
}

Memoization for expensive operations

const memoize = (fn) => {
    const cache = new Map();

    return (...args) => {
        const key = JSON.stringify(args);
        if (cache.has(key)) {
            return cache.get(key);
        }
        const result = fn(...args);
        cache.set(key, result);
        return result;
    };
};

const expensiveOperation = memoize((n) => {
    // Expensive calculation
    return n * n;
});

Security Best Practices

Input validation

function sanitizeInput(input) {
    // Remove potentially dangerous characters
    return input.replace(/[<>]/g, '');
}

function validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
}

function processUserInput(input) {
    const sanitized = sanitizeInput(input);
    if (!validateEmail(sanitized)) {
        throw new Error('Invalid email format');
    }
    return sanitized;
}

Avoid eval()

// Bad
const result = eval('2 + 2');

// Good
const result = 2 + 2;

// If you need dynamic code execution, use Function constructor
const add = new Function('a', 'b', 'return a + b');
console.log(add(2, 3)); // 5

Secure random number generation

// For cryptographic purposes
const crypto = require('crypto');
const randomBytes = crypto.randomBytes(32);
const token = randomBytes.toString('hex');

// For general purposes
const randomNumber = Math.random(); // Not cryptographically secure

Testing

Write testable code

// Good - pure functions are easy to test
function calculateTax(amount, rate) {
    return amount * rate;
}

// Good - dependency injection
class UserService {
    constructor(database) {
        this.database = database;
    }

    async getUser(id) {
        return await this.database.findUser(id);
    }
}

// Bad - hard to test due to tight coupling
class UserService {
    async getUser(id) {
        return await Database.findUser(id); // Direct dependency
    }
}

Use descriptive test names

// Good test names
describe('calculateTax', () => {
    it('should calculate tax correctly for positive amounts', () => {
        expect(calculateTax(100, 0.1)).toBe(10);
    });

    it('should return 0 for zero amount', () => {
        expect(calculateTax(0, 0.1)).toBe(0);
    });
});

Documentation

JSDoc comments

/**
 * Calculates the total price including tax
 * @param {number} price - The base price
 * @param {number} taxRate - The tax rate (e.g., 0.08 for 8%)
 * @returns {number} The total price including tax
 * @throws {TypeError} If price or taxRate is not a number
 */
function calculateTotalPrice(price, taxRate) {
    if (typeof price !== 'number' || typeof taxRate !== 'number') {
        throw new TypeError('Price and tax rate must be numbers');
    }
    return price * (1 + taxRate);
}

Inline comments for complex logic

function complexAlgorithm(data) {
    // Step 1: Filter out invalid entries
    const validData = data.filter(item => item.isValid);

    // Step 2: Sort by priority (higher numbers first)
    validData.sort((a, b) => b.priority - a.priority);

    // Step 3: Apply transformation
    return validData.map(item => ({
        ...item,
        processed: true,
        timestamp: Date.now()
    }));
}

Next Steps

Following these best practices will help you write maintainable, efficient, and secure ECMAScript code. Finally, let's explore resources and further reading to continue your learning journey.