Clean Coding Practices
Summary Overview
Best clean coding practices on js webapps
Updated: 2025-11-24
codingguidemaintenancepractices
Clean Coding Practices Guide
Overview
Clean code is code that is easy to read, understand, maintain, and extend. This guide covers essential principles and practices for writing clean, professional code that stands the test of time.
Core Principles
1. Meaningful Names
Use Descriptive Names
// Bad - Unclear names
function calc(x, y) {
return x y 0.1;
}
// Good - Descriptive names
function calculateTaxAmount(price, taxRate) {
return price taxRate TAX_MULTIPLIER;
}
Avoid Mental Mapping
// Bad - Requires mental mapping
for (let i = 0; i < users.length; i++) {
for (let j = 0; j < users[i].orders.length; j++) {
processOrder(users[i].orders[j]);
}
}
// Good - Clear intent
for (const user of users) {
for (const order of user.orders) {
processOrder(order);
}
}
Use Searchable Names
// Bad - Magic numbers
setTimeout(doSomething, 86400000);
// Good - Named constants
const MILLISECONDS_PER_DAY = 86400000;
setTimeout(doSomething, MILLISECONDS_PER_DAY);
2. Functions Should Be Small
Single Responsibility Principle
// Bad - Multiple responsibilities
function processUser(userData) {
// Validate user data
if (!userData.email || !userData.name) {
throw new Error('Invalid data');
}
// Hash password
const hashedPassword = bcrypt.hash(userData.password, 10);
// Save to database
database.save({
...userData,
password: hashedPassword
});
// Send welcome email
emailService.send(userData.email, 'Welcome!');
// Log activity
logger.info(`User created: ${userData.email}`);
}
// Good - Separated concerns
function validateUserData(userData) {
if (!userData.email || !userData.name) {
throw new Error('Invalid user data');
}
}
function hashPassword(password) {
return bcrypt.hash(password, 10);
}
function saveUser(userData) {
return database.save(userData);
}
function sendWelcomeEmail(email) {
return emailService.send(email, 'Welcome!');
}
function logUserCreation(email) {
logger.info(`User created: ${email}`);
}
async function processUser(userData) {
validateUserData(userData);
const hashedPassword = await hashPassword(userData.password);
const user = await saveUser({ ...userData, password: hashedPassword });
await sendWelcomeEmail(userData.email);
logUserCreation(userData.email);
return user;
}
Limit Function Parameters
// Bad - Too many parameters
function createUser(name, email, age, address, phone, preferences, settings) {
// ...
}
// Good - Use object parameter
function createUser(userConfig) {
const { name, email, age, address, phone, preferences, settings } = userConfig;
// ...
}
// Even better - Use a class or factory
class UserBuilder {
constructor(name, email) {
this.name = name;
this.email = email;
}
withAge(age) {
this.age = age;
return this;
}
withAddress(address) {
this.address = address;
return this;
}
build() {
return new User(this);
}
}
3. Comments and Documentation
Code Should Be Self-Documenting
// Bad - Unnecessary comment
// Increment i by 1
i++;
// Bad - Comment explains what, not why
// Set user status to active
user.status = 'active';
// Good - Comment explains why
// Set user status to active because email verification was successful
user.status = 'active';
Use Comments for Complex Logic
// Good - Explains complex algorithm
function calculateOptimalRoute(points) {
// Using Dijkstra's algorithm to find shortest path
// Time complexity: O(V²) where V is number of vertices
const distances = new Map();
const visited = new Set();
// Initialize distances with infinity except start point
for (const point of points) {
distances.set(point.id, point.isStart ? 0 : Infinity);
}
// ... algorithm implementation
}
4. Consistent Formatting
Use Consistent Indentation
// Bad - Inconsistent indentation
function processData(data) {
if (data) {
const result = data.map(item => {
return {
id: item.id,
name: item.name
};
});
return result;
}
return null;
}
// Good - Consistent indentation
function processData(data) {
if (data) {
const result = data.map(item => ({
id: item.id,
name: item.name
}));
return result;
}
return null;
}
Group Related Code
// Good - Logical grouping
class UserService {
// Constructor and initialization
constructor(database, emailService) {
this.database = database;
this.emailService = emailService;
}
// User creation methods
async createUser(userData) {
// ...
}
async validateUser(userData) {
// ...
}
// User retrieval methods
async getUserById(id) {
// ...
}
async getUserByEmail(email) {
// ...
}
// User update methods
async updateUser(id, updates) {
// ...
}
async deleteUser(id) {
// ...
}
}
5. Error Handling
Use Proper Error Handling
// Bad - Silent failures
function parseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
return null; // Silent failure
}
}
// Good - Explicit error handling
function parseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
throw new Error(`Failed to parse JSON: ${error.message}`);
}
}
// Better - Return result object
function parseJSON(jsonString) {
try {
const data = JSON.parse(jsonString);
return { success: true, data };
} catch (error) {
return { success: false, error: error.message };
}
}
Create Custom Error Types
class ValidationError extends Error {
constructor(field, message) {
super(`Validation failed for ${field}: ${message}`);
this.name = 'ValidationError';
this.field = field;
}
}
class DatabaseError extends Error {
constructor(operation, message) {
super(`Database ${operation} failed: ${message}`);
this.name = 'DatabaseError';
this.operation = operation;
}
}
// Usage
function validateEmail(email) {
if (!email.includes('@')) {
throw new ValidationError('email', 'Must contain @ symbol');
}
}
Code Organization
1. File and Folder Structure
src/
├── components/ # Reusable UI components
│ ├── common/ # Shared components
│ ├── forms/ # Form-specific components
│ └── layout/ # Layout components
├── services/ # Business logic and API calls
│ ├── api/ # API service classes
│ ├── auth/ # Authentication services
│ └── utils/ # Utility services
├── hooks/ # Custom React hooks
├── types/ # TypeScript type definitions
├── constants/ # Application constants
├── utils/ # Pure utility functions
└── tests/ # Test files
├── unit/ # Unit tests
├── integration/ # Integration tests
└── helpers/ # Test helpers
2. Import Organization
// 1. Node modules
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { format } from 'date-fns';
// 2. Internal modules (absolute imports)
import { UserService } from '@/services/UserService';
import { validateEmail } from '@/utils/validation';
import { API_ENDPOINTS } from '@/constants/api';
// 3. Relative imports
import './UserProfile.css';
import { UserAvatar } from '../common/UserAvatar';
3. Consistent Naming Conventions
// Constants - UPPER_SNAKE_CASE
const MAX_RETRY_ATTEMPTS = 3;
const API_BASE_URL = 'https://api.example.com';
// Variables and functions - camelCase
const userProfile = getUserProfile();
const isAuthenticated = checkAuthStatus();
// Classes - PascalCase
class UserService {
constructor() {}
}
// Files and folders - kebab-case
// user-profile.component.js
// api-service.js
// React components - PascalCase
const UserProfile = () => {
return <div>Profile</div>;
};
Testing and Quality
1. Write Testable Code
// Bad - Hard to test
function processUserData() {
const userData = document.getElementById('userForm').value;
const isValid = userData.email.includes('@');
if (isValid) {
fetch('/api/users', {
method: 'POST',
body: JSON.stringify(userData)
});
}
}
// Good - Testable functions
function validateEmail(email) {
return email && email.includes('@');
}
function saveUser(userData, apiClient) {
return apiClient.post('/api/users', userData);
}
function processUserData(userData, apiClient) {
if (validateEmail(userData.email)) {
return saveUser(userData, apiClient);
}
throw new Error('Invalid email');
}
2. Use Descriptive Test Names
// Bad - Unclear test names
test('user test', () => {
// ...
});
test('validation', () => {
// ...
});
// Good - Descriptive test names
describe('UserService', () => {
describe('createUser', () => {
test('should create user with valid data', () => {
// ...
});
test('should throw ValidationError when email is missing', () => {
// ...
});
test('should hash password before saving to database', () => {
// ...
});
});
});
3. Follow AAA Pattern (Arrange, Act, Assert)
test('should calculate total price with tax', () => {
// Arrange
const price = 100;
const taxRate = 0.1;
const expected = 110;
// Act
const result = calculateTotalWithTax(price, taxRate);
// Assert
expect(result).toBe(expected);
});
Development Tools and Automation
1. Code Formatting
// .prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 80,
"tabWidth": 2
}
2. Linting Configuration
// .eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'prettier'
],
rules: {
'no-console': 'warn',
'no-unused-vars': 'error',
'prefer-const': 'error',
'no-var': 'error'
}
};
3. Pre-commit Hooks
// package.json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
".{js,ts,tsx}": [
"eslint --fix",
"prettier --write",
"git add"
]
}
}
Code Metrics and Quality Gates
1. Complexity Metrics
- Cyclomatic Complexity: Keep functions under 10
- Function Length: Aim for functions under 20 lines
- File Length: Keep files under 300 lines
- Parameter Count: Maximum 3-4 parameters per function
2. Code Coverage
# Run tests with coverage
npm test -- --coverage
# Set coverage thresholds
# jest.config.js
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
3. Quality Tools
- SonarQube: Code quality and security analysis
- CodeClimate: Maintainability and test coverage
- ESLint: JavaScript/TypeScript linting
- Prettier: Code formatting
- Husky: Git hooks for quality gates
Best Practices Summary
Do's
- Write self-documenting code
- Use meaningful variable and function names
- Keep functions small and focused
- Handle errors explicitly
- Write tests for your code
- Use consistent formatting
- Follow established conventions
- Refactor regularly
- Use version control effectively
- Document complex business logic
Don'ts
- Don't use magic numbers or strings
- Don't write overly complex functions
- Don't ignore error handling
- Don't skip code reviews
- Don't commit commented-out code
- Don't use unclear abbreviations
- Don't repeat yourself (DRY principle)
- Don't optimize prematurely
- Don't mix different abstraction levels
- Don't write code without tests
Continuous Improvement
Code Review Checklist
- [ ] Code follows naming conventions
- [ ] Functions are small and focused
- [ ] Error handling is implemented
- [ ] Code is properly tested
- [ ] No code duplication
- [ ] Performance considerations addressed
- [ ] Security best practices followed
- [ ] Documentation is adequate
Refactoring Indicators
- Functions longer than 20 lines
- Classes with too many responsibilities
- Duplicate code patterns
- Complex conditional logic
- Long parameter lists
- Deep nesting levels
- Unclear variable names
Remember: Clean code is not written once, but maintained continuously. Regular refactoring and adherence to these principles will result in a codebase that is enjoyable to work with and easy to maintain.