55. Best Practices & Tips: Clean Code JavaScript
Introduction
JavaScript best practices cover naming conventions, code organization, security patterns, and tooling such as ESLint and Prettier that keep code consistent. Applying best practices makes the code easier to read, maintain, and collaborate on across developers.
For IT students, getting into the habit of writing clean code from the start will help you a lot in the professional world. Neat, consistent code is easier for seniors to review and for teams to maintain.
Why Are Best Practices Important?
Code written today will be read and modified by others (including your future self). Best practices ensure the code stays readable, maintainable, and secure even years later.
Naming Conventions
// === Variables and Functions: camelCase ===
const firstName = 'Mike';
const studentCount = 42;
const isActive = true;
function getStudentById(id) { /* ... */ }
function calculateAverageScore(scores) { /* ... */ }
// === Class: PascalCase ===
class StudentService { /* ... */ }
class CourseController { /* ... */ }
// === Constants: UPPER_SNAKE_CASE ===
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = 'https://api.university.edu';
const DEFAULT_PAGE_SIZE = 20;
// === Private members: prefix # ===
class StudentManager {
#cache = new Map();
#config;
#validateStudent(data) { /* ... */ }
}
// === Boolean: is/has/can prefix ===
const isEnrolled = true;
const hasPermission = false;
const canEdit = true;
// === Event handlers: handle prefix ===
function handleSubmit(event) { /* ... */ }
function handleStudentClick(student) { /* ... */ }
// === DOM elements: element type prefix ===
const studentForm = document.getElementById('student-form');
const submitButton = document.getElementById('submit-btn');
const searchInput = document.getElementById('search-input');
const resultsList = document.getElementById('results-list');
// === Functions: verb prefix ===
function fetchStudents() { /* ... */ }
function renderStudentCard(student) { /* ... */ }
function validateStudentID(studentId) { /* ... */ }
function formatCurrency(amount) { /* ... */ }
// === Callbacks: descriptive names ===
students.map(studentToCard); // not: students.map(s => ...)
students.filter(isActiveStudent); // not: students.filter(s => s.active)
DRY (Don't Repeat Yourself)
// === BAD: Code repetition ===
function getActiveTIStudents() {
const response = await fetch('/api/students?majorCode=TI&status=active');
return response.json();
}
function getActiveSIStudents() {
const response = await fetch('/api/students?majorCode=SI&status=active');
return response.json();
}
// === GOOD: DRY with a parameter ===
async function getActiveStudents(majorCode) {
const response = await fetch(
`/api/students?majorCode=${majorCode}&status=active`
);
return response.json();
}
const csStudents = await getActiveStudents('CS');
const siStudents = await getActiveStudents('SI');
// === DRY for validation ===
// BAD
if (!name || name.length < 3) { showError('Name at least 3 characters'); }
if (!studentId || studentId.length !== 10) { showError('StudentID must be 10 digits'); }
if (!email || !email.includes('@')) { showError('Invalid email'); }
// GOOD
const validators = [
{ field: 'name', validate: v => v?.length >= 3, message: 'Name at least 3 characters' },
{ field: 'studentId', validate: v => /^\d{10}$/.test(v), message: 'StudentID must be 10 digits' },
{ field: 'email', validate: v => /^[^\s@]+@[^\s@]+$/.test(v), message: 'Invalid email' }
];
function validateForm(data) {
for (const { field, validate, message } of validators) {
if (!validate(data[field])) {
showError(message);
return false;
}
}
return true;
}
Small Functions (Single Responsibility)
// === BAD: Function too large ===
function processStudentRegistration(formData) {
// Validation (20 lines)
// Save to the database (15 lines)
// Send an email (10 lines)
// Update enrollment (10 lines)
// Generate the card (15 lines)
// Log activity (5 lines)
// Total: 75 lines in one function
}
// === GOOD: Small functions with a single responsibility ===
function validateRegistrationData(data) {
const errors = [];
if (!data.name) errors.push('Name is required');
if (!data.studentId) errors.push('StudentID is required');
return errors;
}
async function saveStudentToDatabase(studentData) {
const response = await fetch('/api/students', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(studentData)
});
return response.json();
}
async function sendRegistrationEmail(student) {
return fetch('/api/email/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: student.email,
template: 'registration',
data: student
})
});
}
function generateStudentCard(student) {
return `
<div class="student-card">
<h3>${student.name}</h3>
<p>StudentID: ${student.studentId}</p>
<p>Major: ${student.majorCode}</p>
</div>
`;
}
async function processStudentRegistration(formData) {
const errors = validateRegistrationData(formData);
if (errors.length > 0) {
return { success: false, errors };
}
const student = await saveStudentToDatabase(formData);
await sendRegistrationEmail(student);
return {
success: true,
student,
card: generateStudentCard(student)
};
}
Immutable Data Patterns
// === Spread operator for immutable updates ===
const student = { name: 'Mike', studentId: '001', gpa: 3.5 };
// Mutation (AVOID)
student.gpa = 3.7;
// Immutable update
const updatedStudent = { ...student, gpa: 3.7 };
// === Array immutable operations ===
let students = ['Mike', 'John', 'Anna'];
// AVOID
students.push('Diana');
students.splice(0, 1);
// Immutable
students = [...students, 'Diana']; // Add
students = students.filter(s => s !== 'Mike'); // Remove
students = students.map(s => s === 'John' ? 'John Updated' : s); // Update
// === Nested immutable update ===
const state = {
user: { name: 'Mike', preferences: { theme: 'light' } },
students: [{ id: 1, name: 'John' }]
};
// AVOID: deep mutation
state.user.preferences.theme = 'dark';
// Immutable: spread cascading
const newState = {
...state,
user: {
...state.user,
preferences: {
...state.user.preferences,
theme: 'dark'
}
}
};
// === Object.freeze for truly immutable ===
const config = Object.freeze({
apiUrl: 'https://api.university.edu',
timeout: 5000,
retries: 3
});
config.apiUrl = 'x'; // Silently fails in strict mode
// === Structured clone for deep freeze ===
function deepFreeze(obj) {
Object.freeze(obj);
Object.getOwnPropertyNames(obj).forEach(prop => {
if (obj[prop] !== null && typeof obj[prop] === 'object') {
deepFreeze(obj[prop]);
}
});
return obj;
}
Avoid Global Scope
// === BAD: Global variables ===
var学生列表 = [];
var当前页 = 1;
// === GOOD: Module pattern ===
const StudentModule = (() => {
let students = [];
let currentPage = 1;
function getStudents() { return [...students]; }
function setStudents(data) { students = data; }
function getCurrentPage() { return currentPage; }
return { getStudents, setStudents, getCurrentPage };
})();
// === GOOD: ES Modules ===
// student.js
let students = [];
export function getStudents() { return [...students]; }
export function setStudents(data) { students = data; }
// === IIFE for isolation ===
(function() {
const privateVar = 'cannot be accessed from outside';
window.publicAPI = {
getData: () => privateVar
};
})();
// === Use block scope ===
if (true) {
let scopedVar = 'only in this block';
const SCOPED_CONST = 'only in this block';
}
// scopedVar is not accessible here
Use Strict
// === Implicit global declaration ===
function example() {
'use strict';
// x = 10; // ReferenceError: x is not defined
// delete Object.prototype; // TypeError
// Safer:
let x = 10;
}
// === ES6 modules are automatically in strict mode ===
// file: student.js (automatically strict)
export function getStudent() {
// Strict mode is active without any declaration
}
// === Classes are automatically strict ===
class Student {
constructor() {
// Strict mode is active
}
}
Linting with ESLint
// === .eslintrc.json ===
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"rules": {
"no-unused-vars": "error",
"no-console": "warn",
"eqeqeq": "error",
"no-var": "error",
"prefer-const": "error",
"prefer-arrow-callback": "error",
"no-throw-literal": "error",
"no-return-assign": "error"
}
}
// === Useful ESLint rules ===
// no-unused-vars: Error when a variable is declared but not used
// eqeqeq: Require === and !==
// no-var: Forbid var, use let/const
// prefer-const: Use const when a variable is never reassigned
// prefer-arrow-callback: Use arrow functions for callbacks
// no-throw-literal: throw must use an Error object
Prettier
// === .prettierrc ===
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
// === Example before Prettier ===
const students=[{name:'Mike',studentId:'001',gpa:3.5},{name:'John',studentId:'002',gpa:3.8}];function getStudent(studentId){return students.find(s=>s.studentId===studentId);}
// === Example after Prettier ===
const students = [
{ name: 'Mike', studentId: '001', gpa: 3.5 },
{ name: 'John', studentId: '002', gpa: 3.8 },
];
function getStudent(studentId) {
return students.find((s) => s.studentId === studentId);
}
Security: XSS Prevention
// === BAD: InnerHTML without sanitization ===
function renderStudent(data) {
// DANGEROUS! User input can contain malicious HTML
document.getElementById('output').innerHTML = `
<h2>${data.name}</h2>
<p>${data.bio}</p>
`;
}
// === GOOD: Text content or sanitization ===
function renderStudentSafe(data) {
const container = document.getElementById('output');
const heading = document.createElement('h2');
heading.textContent = data.name; // Safe
const bio = document.createElement('p');
bio.textContent = data.bio; // Safe
container.appendChild(heading);
container.appendChild(bio);
}
// === HTML sanitization ===
function sanitizeHTML(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function renderWithSanitization(data) {
document.getElementById('output').innerHTML = `
<h2>${sanitizeHTML(data.name)}</h2>
<p>${sanitizeHTML(data.bio)}</p>
`;
}
// === Content Security Policy ===
// Add a CSP header on the server:
// Content-Security-Policy: default-src 'self'; script-src 'self'
Modern Patterns
// === Optional chaining (?.) ===
const student = { name: 'Mike', address: { city: 'New York' } };
const city = student?.address?.city; // 'New York'
const province = student?.address?.province; // undefined (no error)
// === Nullish coalescing (??) ===
const page = null ?? 1; // 1
const count = 0 ?? 10; // 0 (different from ||, which returns 10)
// === Array.at() for negative indexes ===
const arr = [1, 2, 3, 4, 5];
const last = arr.at(-1); // 5
const secondLast = arr.at(-2); // 4
// === Object.hasOwn() ===
const obj = { name: 'Mike' };
if (Object.hasOwn(obj, 'name')) {
console.log('Ada property name');
}
// === Logical assignment operators ===
let x = 0;
x ??= 10; // x = 10 (because x is null/undefined)
x ||= 20; // x = 10 (because x is truthy)
x &&= 30; // x = 30 (because x is truthy)
// === Structured cloning ===
const original = { nested: { value: 42 } };
const clone = structuredClone(original);
clone.nested.value = 100;
console.log(original.nested.value); // 42 (unaffected)
// === Error cause ===
try {
throw new Error('Database error', { cause: { code: 'ECONNREFUSED' } });
} catch (error) {
console.log(error.message); // 'Database error'
console.log(error.cause.code); // 'ECONNREFUSED'
}
Code Organization
// === Good folder structure ===
/*
src/
├── modules/
│ ├── auth/
│ │ ├── AuthModule.js
│ │ ├── AuthService.js
│ │ └── AuthController.js
│ ├── student/
│ │ ├── StudentModule.js
│ │ ├── StudentService.js
│ │ └── StudentController.js
│ └── course/
│ ├── CourseModule.js
│ ├── CourseService.js
│ └── CourseController.js
├── shared/
│ ├── utils/
│ │ ├── formatters.js
│ │ └── validators.js
│ ├── components/
│ │ ├── Modal.js
│ │ └── Toast.js
│ └── Database.js
├── config/
│ └── index.js
└── app.js
*/
// === File organization within a single file ===
// 1. Imports
import { validate } from './utils.js';
import { formatDate } from './formatters.js';
// 2. Constants
const MAX_RETRIES = 3;
const API_URL = '/api/students';
// 3. Class/Function definitions
class StudentService {
// ...
}
// 4. Export
export default StudentService;
Real-World Example: Clean Code Refactor for a Campus Application
// === BEFORE: Spaghetti code ===
function doStuff() {
var data = [];
var x = document.getElementById('input').value;
if (x != '') {
fetch('/api/students?search=' + x).then(function(response) {
response.json().then(function(result) {
var html = '';
for (var i = 0; i < result.length; i++) {
html += '<div>' + result[i].name + '</div>';
}
document.getElementById('output').innerHTML = html;
});
});
}
}
// === AFTER: Clean code refactor ===
// shared/utils/formatters.js
export function formatStudentName(name) {
return sanitizeHTML(name);
}
export function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
// shared/utils/sanitizer.js
export function sanitizeHTML(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// modules/student/StudentSearchService.js
const SEARCH_API = '/api/students';
export async function searchStudents(query) {
if (!query?.trim()) return [];
const response = await fetch(
`${SEARCH_API}?search=${encodeURIComponent(query)}`
);
if (!response.ok) {
throw new Error(`Search failed: ${response.status}`);
}
return response.json();
}
// modules/student/StudentSearchRenderer.js
export function renderStudentList(students) {
if (students.length === 0) {
return '<p class="no-results">No students found</p>';
}
return `
<div class="student-list">
${students.map(renderStudentCard).join('')}
</div>
`;
}
function renderStudentCard(student) {
return `
<div class="student-card" data-studentId="${student.studentId}">
<h3>${sanitizeHTML(student.name)}</h3>
<p>StudentID: ${student.studentId}</p>
<p>${student.majorCode}</p>
</div>
`;
}
// modules/student/StudentSearchController.js
import { searchStudents } from './StudentSearchService.js';
import { renderStudentList } from './StudentSearchRenderer.js';
import { debounce } from '../../shared/utils/formatters.js';
export class StudentSearchController {
#inputEl;
#resultsEl;
#debouncedSearch;
constructor(inputId, resultsId) {
this.#inputEl = document.getElementById(inputId);
this.#resultsEl = document.getElementById(resultsId);
this.#debouncedSearch = debounce(this.#search.bind(this), 300);
this.#init();
}
#init() {
this.#inputEl.addEventListener('input', (e) => {
this.#debouncedSearch(e.target.value);
});
}
async #search(query) {
try {
const students = await searchStudents(query);
this.#resultsEl.innerHTML = renderStudentList(students);
} catch (error) {
console.error('Search error:', error);
this.#resultsEl.innerHTML = '<p class="error">Search failed</p>';
}
}
}
// app.js
import { StudentSearchController } from './modules/student/StudentSearchController.js';
document.addEventListener('DOMContentLoaded', () => {
new StudentSearchController('search-input', 'search-results');
});
Example: Error Handling Pattern
// === Custom Error Classes ===
class AppError extends Error {
constructor(message, code, details = null) {
super(message);
this.name = this.constructor.name;
this.code = code;
this.details = details;
this.timestamp = new Date().toISOString();
}
}
class ValidationError extends AppError {
constructor(field, message) {
super(`Validation error: ${message}`, 'VALIDATION_ERROR', { field });
}
}
class APIError extends AppError {
constructor(status, message, endpoint) {
super(message, 'API_ERROR', { status, endpoint });
}
}
class NotFoundError extends AppError {
constructor(resource, id) {
super(`${resource} with id ${id} not found`, 'NOT_FOUND', { resource, id });
}
}
// === Error handler pattern ===
class ErrorHandler {
static handle(error, context = '') {
console.group(`[ERROR] ${context}`);
console.error('Type:', error.name);
console.error('Message:', error.message);
console.error('Code:', error.code);
console.error('Details:', error.details);
console.error('Stack:', error.stack);
console.groupEnd();
if (error instanceof ValidationError) {
showFieldError(error.details.field, error.message);
} else if (error instanceof APIError) {
showNotification('error', `Server error: ${error.message}`);
} else {
showNotification('error', 'An error occurred. Please try again.');
}
// Log to the server (in production)
// logErrorToServer(error, context);
}
}
// === Usage ===
async function enrollStudent(studentId, courseId) {
try {
if (!studentId) throw new ValidationError('studentId', 'StudentID is required');
if (!courseId) throw new ValidationError('courseId', 'Course ID is required');
const response = await fetch('/api/enroll', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ studentId, courseId })
});
if (!response.ok) {
const data = await response.json();
throw new APIError(response.status, data.message, '/api/enroll');
}
return await response.json();
} catch (error) {
ErrorHandler.handle(error, 'enrollStudent');
throw error;
}
}
Best Practices Checklist
// ✅ DO:
// - Use const by default, let when reassignment is needed
// - Use === and !== (not == and !=)
// - Use arrow functions for callbacks
// - Use template literals for string interpolation
// - Use destructuring to extract values
// - Use async/await for promises
// - Use optional chaining (?.) and nullish coalescing (??)
// - Sanitize user input before rendering to the DOM
// - Handle errors with try/catch or .catch()
// - Separate concerns: one function = one task
// - Use descriptive variable and function names
// ❌ DON'T:
// - Use var (use let/const instead)
// - Use == or !=
// - Use innerHTML with user input without sanitization
// - Hardcode secrets or API keys on the client side
// - Use eval() or the Function() constructor
// - Ignore the return value of a Promise
// - Write functions that are too long (> 50 lines)
// - Use global variables
// - Copy-paste code; use functions
// - Ignore error handling
Code Explanation
Naming Conventions
- Variables/Functions: camelCase (
studentName,getStudentById) - Classes: PascalCase (
StudentService,CourseController) - Constants: UPPER_SNAKE_CASE (
MAX_RETRY_COUNT,API_URL) - Boolean: is/has/can prefix (
isActive,hasPermission) - DOM elements: type prefix (
studentForm,submitButton)
Code Quality
- DRY: Don't repeat code; abstract it into functions
- Single Responsibility: One function = one task
- Small Functions: Functions under 30 lines
- Immutability: Don't mutate data directly
Security
- Sanitize user input before rendering to the DOM
- Use
textContentinstead ofinnerHTMLfor text - Implement CSP headers
- Don't hardcode secrets
Tooling
- ESLint: Static analysis to catch errors
- Prettier: Auto-formatting for style consistency
- Both must be present in every project
Conclusion
JavaScript best practices are not just theory; they are a long-term investment in a healthy codebase. With consistent naming conventions, the DRY principle, single responsibility, and tooling like ESLint + Prettier, your code will be easier to read, maintain, and collaborate on. For a campus application, start refactoring legacy code incrementally and make it a habit to write clean code from the start of every new project.