Clean Code: Naming Conventions Guide for Better Readability and Maintainability

birdgang
4 min readOct 2, 2024

--

Proper naming is a cornerstone of clean code. When we adhere to established naming patterns, it greatly enhances code readability, maintainability, and the overall developer experience. Below are the naming conventions commonly used in various programming patterns, with Node.js examples for each.

1. Function/Method Naming

Function or method names should clearly describe the action they perform. Use verbs in the names to indicate behavior and include specific terms to express intent.

Naming Pattern :

  • Use a verb + noun structure ( getUserData, processTransaction ).
  • For state-checking or condition validation methods, use prefixes like is, has, or can.
// Bad example:
function data(user) {
// ... implementation
return;
}

// Improved example:
function getUserData(user) {
// Retrieves user data from the database
return;
}

// State-checking function example:
function isValidEmail(email) {
// Validates the format of the email
return true;
}

Common Verbs Used :

  • CRUD : create, read, update, delete
  • Data Handling : process, calculate, convert, parse
  • Data Retrieval : fetch, retrieve, get
  • Condition Checking : is, has, can, should
  • Event Handling : trigger, invoke, emit

2. Variable Naming

Variable names should clearly represent the data they hold. Using specific and meaningful names, even if they are slightly longer, is more important than using short and unclear names.

Naming Pattern:

  • Use noun names for variables (user, transaction, orderList).
  • For boolean values, use prefixes like is, has, or should (isCompleted, hasAccess).
  • Avoid abbreviations; prefer index, counter over i, j.
// Bad example:
let x = 100;
let list1 = [1, 2, 3];

// Improved example:
let maxRetryCount = 100;
let userScores = [1, 2, 3];

Frequently Used Variable Names:

  • Collections : users, transactions, itemList, orderQueue
  • Booleans : isActive, hasPermission, shouldProcess
  • Temporary Values : currentCount, totalAmount, averageScore

3. Class/Object Naming

Class or object names should reflect a concept, entity, or role. A good class name makes the class’s responsibility clear.

Naming Pattern:

  • Use nouns or noun phrases (User, TransactionManager, EmailValidator).
  • End with the appropriate role indicator (Controller, Manager, Repository, Service).
  • Ensure single responsibility and clear intent (OrderProcessor, UserAuthenticator).
// Bad example:
class DoStuff {
// ... implementation
}

// Improved example:
class OrderProcessor {
// Handles the order processing logic
// ... implementation
}

// Role-based class example:
class EmailValidator {
// Class to validate email formats
validate(email) {
// Email validation logic
return true;
}
}

4. Constant/Global Variable Naming

Constants should use uppercase letters with underscores (UPPERCASE_WITH_UNDERSCORE). Their names should clearly describe their roles to avoid confusion.

Naming Pattern:

  • Global constants : MAX_RETRY, DEFAULT_TIMEOUT, MIN_PASSWORD_LENGTH
  • Configuration values or environment variables : DATABASE_URL, API_KEY
// Bad example:
const maxval = 100;

// Improved example:
const MAX_RETRY_COUNT = 100;
const DEFAULT_PAGE_SIZE = 20;

5. Interface/Abstract Class Naming

Interface and abstract class names should clearly indicate their role. Use the prefix I or the suffix able to make it explicit that they are abstract or define behavior.

Naming Pattern:

  • I + role name (IUserRepository, IDataParser)
  • Role + able (Runnable, Serializable, Cloneable)
// Interface example:
class IUserRepository {
getUserById(userId) {
throw new Error('Method not implemented');
}
}

// Abstract class example:
class PaymentProcessor {
processPayment(order) {
throw new Error('Method not implemented');
}
}

6. Test Method Naming

Test methods should clearly express the action under test and the expected outcome. This makes it easier to understand what’s being validated at a glance.

Naming Pattern :

  • Method being tested + Condition + Expected Outcome (calculateTotal_shouldReturnZero_whenCartIsEmpty)
  • Given-When-Then structure (givenUser_whenEmailInvalid_thenThrowException)
// Bad example:
function test1() {
return;
}

// Improved example:
function calculateTotal_shouldReturnZero_whenCartIsEmpty() {
// Test that total is zero when the cart is empty
return;
}

function givenUser_whenEmailInvalid_thenThrowException() {
// Test that an exception is thrown for invalid email format
return;
}

7. Module/Package Naming

Modules and packages should use lowercase letters separated by underscores. Choose descriptive names that convey the module’s role to prevent name collisions.

Naming Pattern :

  • Use underscores (_) to separate words (user_management, transaction_processing).
  • Name based on functionality (auth, api, service, controller).
// Bad example:
import MyModule from 'Project';

// Improved example:
import userManagement from 'project/user_management';
import paymentService from 'project/payment_service';

8. Custom Exception Class Naming

Custom exception classes should end with Exception or Error to make it clear that they represent an exceptional condition.

Naming Pattern :

  • Role name + Exception (InvalidUserException, DataNotFoundException)
  • Role name + Error (FileReadError, TransactionProcessingError)
// Bad example:
class MyError extends Error {
}

// Improved example:
class InvalidUserException extends Error {
// Exception thrown when the user is invalid
}

class DataNotFoundException extends Error {
// Exception thrown when data is not found
}

These patterns and naming conventions will help ensure consistent, clean, and maintainable code across your projects. Establishing and adhering to a clear set of guidelines early on in development can make collaboration smoother and codebases easier to understand for everyone involved.

--

--