Clean Coding Practices Guide

Summary Overview

Practical habits and code samples from the original portfolio docs explaining how to keep functions tiny, names meaningful, and logic easy to maintain.

Updated: 2025-11-24
clean-codemaintainabilitybest-practices

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;
}
// 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.