Skip to content

Latest commit

Β 

History

History
366 lines (304 loc) Β· 8.32 KB

File metadata and controls

366 lines (304 loc) Β· 8.32 KB

Code Structure Best Practices

"The structure of your code today determines the maintainability of your system tomorrow."

Table of Contents

  1. Modular Design
  2. Object-Oriented Programming
  3. Function Decomposition
  4. File Organization

1. Modular Design

πŸ’‘ Principle

Break code into smaller, reusable modules with clear responsibilities. Each module should be independent and interchangeable.

✨ Key Points

  • Single Responsibility Principle
  • High Cohesion, Low Coupling
  • Clear Module Interfaces
  • Dependency Management

πŸ“ Examples

Node.js

❌ Poor: Everything in one file

// app.js - Everything mixed together
const express = require('express');
const app = express();
const mongoose = require('mongoose');

// Database connection
mongoose.connect('mongodb://localhost/myapp');

// User model
const userSchema = new mongoose.Schema({
  name: String,
  email: String
});
const User = mongoose.model('User', userSchema);

// Routes
app.post('/register', async (req, res) => {
  const user = new User(req.body);
  await user.save();
  res.send(user);
});

app.listen(3000);

βœ… Clean: Modular Structure

// config/database.js
const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI);
    console.log('Database connected successfully');
  } catch (error) {
    console.error('Database connection failed:', error.message);
    process.exit(1);
  }
};

module.exports = connectDB;

// models/User.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: String,
  email: String
}, { timestamps: true });

module.exports = mongoose.model('User', userSchema);

// controllers/userController.js
const User = require('../models/User');

exports.register = async (req, res) => {
  try {
    const user = await User.create(req.body);
    res.status(201).json({ success: true, data: user });
  } catch (error) {
    res.status(400).json({ success: false, error: error.message });
  }
};

// routes/userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');

router.post('/register', userController.register);

module.exports = router;

// app.js
const express = require('express');
const connectDB = require('./config/database');
const userRoutes = require('./routes/userRoutes');

const app = express();
connectDB();

app.use('/api/users', userRoutes);
app.listen(3000);

2. Object-Oriented Programming

πŸ’‘ Principle

Use classes and objects to create maintainable, reusable, and extensible code structures.

✨ Key Points

  • Encapsulation
  • Inheritance
  • Polymorphism
  • Interface Segregation

πŸ“ Examples

Node.js

❌ Poor: No Structure

const processPayment = (type, amount) => {
  if (type === 'credit') {
    // Process credit payment
  } else if (type === 'paypal') {
    // Process PayPal payment
  }
};

βœ… Clean: OOP Structure

// Abstract base class
class PaymentProcessor {
  constructor(amount) {
    this.amount = amount;
  }

  async process() {
    throw new Error('Process method must be implemented');
  }

  validate() {
    if (this.amount <= 0) {
      throw new Error('Invalid amount');
    }
  }
}

class CreditCardProcessor extends PaymentProcessor {
  constructor(amount, cardDetails) {
    super(amount);
    this.cardDetails = cardDetails;
  }

  async process() {
    this.validate();
    // Process credit card payment
    return { success: true, method: 'credit' };
  }
}

class PayPalProcessor extends PaymentProcessor {
  constructor(amount, paypalEmail) {
    super(amount);
    this.paypalEmail = paypalEmail;
  }

  async process() {
    this.validate();
    // Process PayPal payment
    return { success: true, method: 'paypal' };
  }
}

// Usage
const processPayment = async (processor) => {
  try {
    return await processor.process();
  } catch (error) {
    console.error('Payment failed:', error.message);
    return { success: false, error: error.message };
  }
};

3. Function Decomposition

πŸ’‘ Principle

Break down complex operations into smaller, focused functions that each do one thing well.

✨ Key Points

  • Single Responsibility
  • Maximum 20-30 Lines per Function
  • Clear Input/Output
  • Descriptive Names

πŸ“ Examples

Node.js

❌ Poor: Monolithic Function

async function handleUserRegistration(data) {
  // Validate input
  if (!data.email || !data.password || !data.name) {
    throw new Error('Missing required fields');
  }
  if (data.password.length < 8) {
    throw new Error('Password too short');
  }
  if (!data.email.includes('@')) {
    throw new Error('Invalid email');
  }

  // Hash password
  const salt = await bcrypt.genSalt(10);
  const hashedPassword = await bcrypt.hash(data.password, salt);

  // Create user
  const user = new User({
    email: data.email,
    password: hashedPassword,
    name: data.name
  });

  // Save user
  await user.save();

  // Send welcome email
  const msg = {
    to: data.email,
    subject: 'Welcome!',
    text: `Welcome ${data.name}!`
  };
  await sendEmail(msg);

  return user;
}

βœ… Clean: Decomposed Functions

class UserRegistrationService {
  validateInput(data) {
    if (!data.email || !data.password || !data.name) {
      throw new Error('Missing required fields');
    }
    this.validatePassword(data.password);
    this.validateEmail(data.email);
  }

  validatePassword(password) {
    if (password.length < 8) {
      throw new Error('Password too short');
    }
  }

  validateEmail(email) {
    if (!email.includes('@')) {
      throw new Error('Invalid email');
    }
  }

  async hashPassword(password) {
    const salt = await bcrypt.genSalt(10);
    return bcrypt.hash(password, salt);
  }

  async createUser(data, hashedPassword) {
    const user = new User({
      email: data.email,
      password: hashedPassword,
      name: data.name
    });
    return user.save();
  }

  async sendWelcomeEmail(email, name) {
    const msg = {
      to: email,
      subject: 'Welcome!',
      text: `Welcome ${name}!`
    };
    return sendEmail(msg);
  }

  async register(data) {
    try {
      this.validateInput(data);
      const hashedPassword = await this.hashPassword(data.password);
      const user = await this.createUser(data, hashedPassword);
      await this.sendWelcomeEmail(data.email, data.name);
      return user;
    } catch (error) {
      throw new Error(`Registration failed: ${error.message}`);
    }
  }
}

4. File Organization

πŸ’‘ Principle

Organize files in a logical, consistent structure that makes it easy to locate and maintain code.

✨ Key Points

  • Group Related Files
  • Consistent Naming
  • Clear Directory Structure
  • Separation by Feature/Module

πŸ“ Example Project Structure

project-root/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   β”œβ”€β”€ database.js
β”‚   β”‚   └── app.js
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ User.js
β”‚   β”‚   └── Product.js
β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   β”œβ”€β”€ userController.js
β”‚   β”‚   └── productController.js
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ userService.js
β”‚   β”‚   └── emailService.js
β”‚   β”œβ”€β”€ routes/
β”‚   β”‚   β”œβ”€β”€ userRoutes.js
β”‚   β”‚   └── productRoutes.js
β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   β”œβ”€β”€ auth.js
β”‚   β”‚   └── validation.js
β”‚   └── utils/
β”‚       β”œβ”€β”€ logger.js
β”‚       └── helpers.js
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ unit/
β”‚   └── integration/
β”œβ”€β”€ public/
β”‚   β”œβ”€β”€ css/
β”‚   └── js/
└── package.json

πŸ”‘ Best Practices

  1. Group by Feature: Keep related files together
  2. Clear Naming: Use consistent, descriptive names
  3. Shallow Nesting: Avoid deep directory structures
  4. Logical Separation: Separate concerns into appropriate directories

πŸ“š Additional Resources