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.