devbook
Advanced Topics

Software Maintainability

Writing maintainable code that stands the test of time

Software Maintainability

Maintainable code is easy to understand, modify, and extend over time.

Clean Code Principles

Meaningful Names

// ❌ Poor names
const d = new Date()
const x = users.filter(u => u.a)

// ✅ Clear names
const currentDate = new Date()
const activeUsers = users.filter(user => user.isActive)

Functions Should Do One Thing

// ❌ Does too much
function processUserAndSendEmail(user: User) {
  validateUser(user)
  saveToDatabase(user)
  sendWelcomeEmail(user)
  logAnalytics(user)
}

// ✅ Single responsibility
function registerUser(user: User) {
  validateUser(user)
  return saveToDatabase(user)
}

function sendWelcomeToNewUser(user: User) {
  sendWelcomeEmail(user)
  logUserRegistration(user)
}

Small Functions

// Keep functions short and focused
function calculateDiscount(price: number, userType: UserType): number {
  if (isPremiumUser(userType)) {
    return applyPremiumDiscount(price)
  }
  
  if (isFirstTimeBuyer(userType)) {
    return applyFirstTimeDiscount(price)
  }
  
  return price
}

Code Organization

File Structure

src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.test.tsx
│   │   ├── Button.stories.tsx
│   │   └── index.ts
│   └── Card/
│       ├── Card.tsx
│       └── index.ts
├── hooks/
│   ├── useAuth.ts
│   └── useData.ts
├── lib/
│   ├── api.ts
│   ├── utils.ts
│   └── constants.ts
├── types/
│   └── index.ts
└── app/
    ├── page.tsx
    └── layout.tsx

Module Organization

// ✅ Group related functionality
// user/
//   ├── user.types.ts
//   ├── user.service.ts
//   ├── user.repository.ts
//   ├── user.validator.ts
//   └── index.ts

// Export clean interfaces
export { UserService } from './user.service'
export type { User, CreateUserDTO } from './user.types'

Comments & Documentation

When to Comment

// ❌ Unnecessary comments
// Increment i
i++

// Get user
const user = getUser()

// ✅ Useful comments
// Apply discount: Premium users get 20% off, first-time buyers get 10% off
const finalPrice = calculateDiscount(basePrice, user.type)

// Workaround for Safari bug: https://bugs.webkit.org/show_bug.cgi?id=12345
if (isSafari) {
  applyWorkaround()
}

JSDoc for Functions

/**
 * Calculates the total price including tax and discounts
 * 
 * @param basePrice - The original price before modifications
 * @param taxRate - Tax rate as a decimal (e.g., 0.08 for 8%)
 * @param discountCode - Optional discount code to apply
 * @returns The final price after tax and discounts
 * 
 * @example
 * ```ts
 * const total = calculateTotal(100, 0.08, 'SAVE10')
 * // Returns: 97.2 (100 - 10% discount + 8% tax)
 * ```
 */
function calculateTotal(
  basePrice: number,
  taxRate: number,
  discountCode?: string
): number {
  // Implementation
}

Error Handling

Custom Errors

class ValidationError extends Error {
  constructor(
    message: string,
    public field: string
  ) {
    super(message)
    this.name = 'ValidationError'
  }
}

class NotFoundError extends Error {
  constructor(resource: string, id: string) {
    super(`${resource} with id ${id} not found`)
    this.name = 'NotFoundError'
  }
}

Error Handling Pattern

// ❌ Silent failures
try {
  await saveUser(user)
} catch (error) {
  // Ignored
}

// ✅ Proper error handling
try {
  await saveUser(user)
} catch (error) {
  if (error instanceof ValidationError) {
    return { error: error.message, field: error.field }
  }
  
  logger.error('Failed to save user', { error, userId: user.id })
  throw error
}

Type Safety

Use TypeScript Effectively

// ✅ Strict types
interface User {
  id: string
  email: string
  role: 'admin' | 'user' | 'guest'
  preferences: UserPreferences
}

interface UserPreferences {
  theme: 'light' | 'dark'
  notifications: boolean
}

// ✅ Avoid 'any'
function processData<T>(data: T): T {
  return data
}

// ✅ Use discriminated unions
type Result<T> =
  | { success: true; data: T }
  | { success: false; error: string }

function getUser(id: string): Result<User> {
  // Implementation
}

Testing for Maintainability

Write Testable Code

// ❌ Hard to test
class UserService {
  async createUser(data: CreateUserDTO) {
    const user = await database.insert(data)
    await emailService.send(user.email, 'Welcome!')
    return user
  }
}

// ✅ Easy to test (dependency injection)
class UserService {
  constructor(
    private db: Database,
    private emailService: EmailService
  ) {}
  
  async createUser(data: CreateUserDTO) {
    const user = await this.db.users.create(data)
    await this.emailService.sendWelcome(user.email)
    return user
  }
}

Code Duplication

DRY Principle

// ❌ Duplication
function formatUserName(user: User) {
  return `${user.firstName} ${user.lastName}`.trim()
}

function formatAuthorName(author: Author) {
  return `${author.firstName} ${author.lastName}`.trim()
}

// ✅ Extract common logic
function formatFullName(person: { firstName: string; lastName: string }) {
  return `${person.firstName} ${person.lastName}`.trim()
}

When Duplication is OK

// Sometimes similar-looking code serves different purposes
function validateUserEmail(email: string) {
  // User-specific validation rules
  if (!email.includes('@')) return false
  if (email.length < 5) return false
  return true
}

function validateAdminEmail(email: string) {
  // Admin-specific validation rules
  if (!email.includes('@company.com')) return false
  return validateUserEmail(email)
}

Refactoring

Extract Method

// Before
function processOrder(order: Order) {
  let total = 0
  for (const item of order.items) {
    total += item.price * item.quantity
  }
  
  if (order.user.isPremium) {
    total *= 0.9
  }
  
  total *= 1.08 // tax
  
  return total
}

// After
function processOrder(order: Order) {
  const subtotal = calculateSubtotal(order.items)
  const discounted = applyDiscount(subtotal, order.user)
  return applyTax(discounted)
}

Extract Variable

// Before
if (user.age > 18 && user.hasLicense && user.insurance.isActive) {
  // Allow rental
}

// After
const isEligibleForRental = 
  user.age > 18 && 
  user.hasLicense && 
  user.insurance.isActive

if (isEligibleForRental) {
  // Allow rental
}

Dependencies

Keep Dependencies Minimal

// ❌ Too many dependencies
import _ from 'lodash'
import moment from 'moment'
import axios from 'axios'
import uuid from 'uuid'

// ✅ Use native alternatives when possible
const uniqueId = crypto.randomUUID()
const now = new Date()

Dependency Injection

// ✅ Inject dependencies
interface Logger {
  info(message: string): void
  error(message: string, error: Error): void
}

class UserService {
  constructor(
    private logger: Logger,
    private db: Database
  ) {}
  
  async createUser(data: CreateUserDTO) {
    try {
      const user = await this.db.users.create(data)
      this.logger.info(`User created: ${user.id}`)
      return user
    } catch (error) {
      this.logger.error('Failed to create user', error)
      throw error
    }
  }
}

Technical Debt

Managing Technical Debt

  • Document known issues
  • Track in issue tracker
  • Prioritize based on impact
  • Allocate time for refactoring
  • Don't let perfect be enemy of good

TODO Comments

// TODO: Optimize this query for large datasets
// See: https://github.com/org/repo/issues/123
function fetchAllUsers() {
  return db.users.findMany()
}

Code Review for Maintainability

Review Checklist

  • Code is easy to understand
  • Functions are small and focused
  • Names are clear and descriptive
  • No unnecessary complexity
  • Adequate test coverage
  • Error handling is proper
  • No code duplication
  • Types are well-defined

Metrics

Cyclomatic Complexity

Keep functions simple (complexity < 10)

Code Coverage

Aim for >80% for critical paths

Code Churn

High churn may indicate design issues

Tools

  • Linters: ESLint, Biome
  • Formatters: Prettier
  • Type Checkers: TypeScript
  • Code Quality: SonarQube, Code Climate
  • Dependency Analysis: Dependency Cruiser