Code Quality Best Practices for Teams
Good code is not just about functionality — it's about readability, maintainability, and collaboration. These practices help teams write code that stands the test of time.
Naming Conventions
Names should be self-documenting. If you need a comment to explain what a variable is, rename it:
// Bad
const x = users.filter(u => u.s === 'active');
const d = new Date();
const tmp = processData(data);
// Good
const activeUsers = users.filter(user => user.status === 'active');
const currentTimestamp = new Date();
const processedData = processData(rawData);
Variable names: nouns, descriptive (userData, totalAmount)
Function names: verbs or verb phrases (fetchUser, calculateTotal, isValidEmail)
Constants: ALL_CAPS with underscores (MAX_RETRY_COUNT, DEFAULT_TIMEOUT)
Functions: Do One Thing
Each function should do one thing well. A good test: if you need "and" to describe what it does, split it:
// Bad: Does multiple things
function processUser(user) {
validateUser(user); // validation
saveToDatabase(user); // persistence
sendWelcomeEmail(user); // side effect
return user;
}
// Good: Single responsibility
function validateUser(user) { ... }
function saveUser(user) { ... }
function notifyUser(user, type) { ... }
function registerUser(user) {
validateUser(user);
const savedUser = saveUser(user);
notifyUser(savedUser, 'welcome');
return savedUser;
}
Keep Functions Small
Ideal: less than 20 lines. Good: less than 50 lines. If a function exceeds 100 lines, strongly consider splitting it.
// Bad: Function doing too much
async function handleOrder(order) {
// 200+ lines of validation, calculation, persistence, notification...
}
// Good: Composed of smaller functions
async function handleOrder(order) {
const validated = validateOrder(order);
const calculated = calculatePricing(validated);
const saved = await persistOrder(calculated);
await notifyCustomer(saved);
return saved;
}
Avoid Magic Numbers
// Bad: What do these numbers mean?
if (user.age > 18 && retryCount < 3 && timeout > 5000) {
processPayment(amount * 0.95);
}
// Good: Named constants explain the intent
const MINIMUM_AGE = 18;
const MAX_RETRY_COUNT = 3;
const PAYMENT_TIMEOUT_MS = 5000;
const LOYALTY_DISCOUNT = 0.05;
if (user.age > MINIMUM_AGE && retryCount < MAX_RETRY_COUNT && timeout > PAYMENT_TIMEOUT_MS) {
processPayment(amount * (1 - LOYALTY_DISCOUNT));
}
Early Returns (Guard Clauses)
Handle edge cases and invalid inputs at the start with early returns:
// Bad: Nested conditionals
function processUser(user) {
if (user) {
if (user.email) {
if (user.isActive) {
// main logic here, deeply nested
}
}
}
}
// Good: Early returns for edge cases
function processUser(user) {
if (!user) return null;
if (!user.email) return null;
if (!user.isActive) return null;
// main logic at top level
return processActiveUser(user);
}
Error Handling
Handle errors explicitly. Never swallow exceptions silently:
// Bad: Silently ignoring errors
try {
await sendEmail(user.email);
} catch (e) {
// Nothing happens, error is lost
}
// Good: Explicit handling
try {
await sendEmail(user.email);
} catch (error) {
logger.error('Failed to send email', { userId: user.id, error });
throw new EmailDeliveryError('Failed to send notification', { cause: error });
}
// Or handle gracefully
try {
await sendEmail(user.email);
} catch (error) {
logger.warn('Email failed, will retry', { userId: user.id });
await queueForRetry(user.id);
}
Comments That Add Value
Good comments explain why, not what. The code shows what, comments explain why:
// Bad: Comments that state the obvious
// Increment counter by 1
counter++;
// Loop through users
for (const user of users) { }
// Good: Comments explain non-obvious decisions
// Using linear search here despite O(n) because:
// 1. Array is guaranteed to be small (< 20 items)
// 2. Avoiding the overhead of building a Map
// 3. Code clarity outweighs micro-optimization
for (const user of users) {
if (user.id === targetId) return user;
}
// Explaining business rules
// Per legal requirement: apply 8% tax for orders > $100
// See: Tax Compliance Doc #2847
const taxRate = order.total > 100 ? 0.08 : 0;
Consistent Code Formatting
Use automated formatting. Debating formatting in code reviews is a waste of time:
// Use Prettier for JavaScript/TypeScript
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100
}
// Use ESLint for code quality rules
// .eslintrc
{
"extends": ["eslint:recommended"],
"rules": {
"no-unused-vars": "error",
"prefer-const": "error"
}
}
Type Safety
Use TypeScript or JSDoc for JavaScript. Types catch bugs at compile time:
// Bad: Any type, no safety
function processOrder(order) {
return order.total * order.quantity; // What if these are undefined?
}
// Good: Explicit types
interface OrderLineItem {
total: number;
quantity: number;
}
interface Order {
items: OrderLineItem[];
customerId: string;
}
function processOrder(order: Order): number {
return order.items.reduce((sum, item) => sum + item.total * item.quantity, 0);
}
Writing Testable Code
Testable code is usually better code. Characteristics:
- Pure functions: Same input → Same output, no side effects
- Dependency injection: Pass dependencies as parameters
- Small, focused functions: Easy to test in isolation
- Single responsibility: Clear input/output
// Hard to test: dependencies hardcoded
function getUser() {
const db = new Database(); // Hardcoded
return db.query('SELECT * FROM users');
}
// Easy to test: dependency injection
function getUser(db: Database) {
return db.query('SELECT * FROM users');
}
// Even better: pass interface
interface UserRepository {
findById(id: string): Promise;
}
function getUser(repo: UserRepository, id: string) {
return repo.findById(id);
}
Code Review Checklist
When reviewing code, check for:
- Does the code do what the PR claims?
- Are edge cases handled?
- Is error handling appropriate?
- Are there tests for new logic?
- Are names descriptive?
- Is the code simpler than before?
- Any security concerns?
- Any performance issues?
The Boy Scout Rule
Leave the code a little better than you found it. Every PR is an opportunity to improve the surrounding code slightly:
- Rename a poorly-named variable while changing the function
- Extract a magic number while adding new logic
- Add a missing test while fixing a bug
Small improvements compound. The codebase gets better over time, not worse.