init
This commit is contained in:
@@ -0,0 +1,659 @@
|
||||
# Backend Code Quality
|
||||
|
||||
SOLID principles, design patterns, clean code practices, and refactoring strategies (2025).
|
||||
|
||||
## SOLID Principles
|
||||
|
||||
### Single Responsibility Principle (SRP)
|
||||
|
||||
**Concept:** Class/module should have one reason to change
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
class User {
|
||||
saveToDatabase() { /* ... */ }
|
||||
sendWelcomeEmail() { /* ... */ }
|
||||
generateReport() { /* ... */ }
|
||||
validateInput() { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Good:**
|
||||
```typescript
|
||||
class User {
|
||||
constructor(public id: string, public email: string, public name: string) {}
|
||||
}
|
||||
|
||||
class UserRepository {
|
||||
async save(user: User) { /* ... */ }
|
||||
async findById(id: string) { /* ... */ }
|
||||
}
|
||||
|
||||
class EmailService {
|
||||
async sendWelcomeEmail(user: User) { /* ... */ }
|
||||
}
|
||||
|
||||
class UserValidator {
|
||||
validate(userData: any) { /* ... */ }
|
||||
}
|
||||
|
||||
class ReportGenerator {
|
||||
generateUserReport(user: User) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Open/Closed Principle (OCP)
|
||||
|
||||
**Concept:** Open for extension, closed for modification
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
class PaymentProcessor {
|
||||
process(amount: number, method: string) {
|
||||
if (method === 'stripe') {
|
||||
// Stripe logic
|
||||
} else if (method === 'paypal') {
|
||||
// PayPal logic
|
||||
}
|
||||
// Adding new payment method requires modifying this class
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Good (Strategy Pattern):**
|
||||
```typescript
|
||||
interface PaymentStrategy {
|
||||
process(amount: number): Promise<PaymentResult>;
|
||||
}
|
||||
|
||||
class StripePayment implements PaymentStrategy {
|
||||
async process(amount: number) {
|
||||
// Stripe-specific logic
|
||||
return { success: true, transactionId: '...' };
|
||||
}
|
||||
}
|
||||
|
||||
class PayPalPayment implements PaymentStrategy {
|
||||
async process(amount: number) {
|
||||
// PayPal-specific logic
|
||||
return { success: true, transactionId: '...' };
|
||||
}
|
||||
}
|
||||
|
||||
class PaymentProcessor {
|
||||
constructor(private strategy: PaymentStrategy) {}
|
||||
|
||||
async process(amount: number) {
|
||||
return this.strategy.process(amount);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const processor = new PaymentProcessor(new StripePayment());
|
||||
await processor.process(100);
|
||||
```
|
||||
|
||||
### Liskov Substitution Principle (LSP)
|
||||
|
||||
**Concept:** Subtypes must be substitutable for base types
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
class Bird {
|
||||
fly() { /* ... */ }
|
||||
}
|
||||
|
||||
class Penguin extends Bird {
|
||||
fly() {
|
||||
throw new Error('Penguins cannot fly!');
|
||||
}
|
||||
}
|
||||
|
||||
// Violates LSP - Penguin breaks Bird contract
|
||||
```
|
||||
|
||||
**Good:**
|
||||
```typescript
|
||||
interface Bird {
|
||||
move(): void;
|
||||
}
|
||||
|
||||
class FlyingBird implements Bird {
|
||||
move() {
|
||||
this.fly();
|
||||
}
|
||||
private fly() { /* ... */ }
|
||||
}
|
||||
|
||||
class Penguin implements Bird {
|
||||
move() {
|
||||
this.swim();
|
||||
}
|
||||
private swim() { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Interface Segregation Principle (ISP)
|
||||
|
||||
**Concept:** Clients shouldn't depend on interfaces they don't use
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
interface Worker {
|
||||
work(): void;
|
||||
eat(): void;
|
||||
sleep(): void;
|
||||
}
|
||||
|
||||
class Robot implements Worker {
|
||||
work() { /* ... */ }
|
||||
eat() { throw new Error('Robots don't eat'); }
|
||||
sleep() { throw new Error('Robots don't sleep'); }
|
||||
}
|
||||
```
|
||||
|
||||
**Good:**
|
||||
```typescript
|
||||
interface Workable {
|
||||
work(): void;
|
||||
}
|
||||
|
||||
interface Eatable {
|
||||
eat(): void;
|
||||
}
|
||||
|
||||
interface Sleepable {
|
||||
sleep(): void;
|
||||
}
|
||||
|
||||
class Human implements Workable, Eatable, Sleepable {
|
||||
work() { /* ... */ }
|
||||
eat() { /* ... */ }
|
||||
sleep() { /* ... */ }
|
||||
}
|
||||
|
||||
class Robot implements Workable {
|
||||
work() { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Dependency Inversion Principle (DIP)
|
||||
|
||||
**Concept:** Depend on abstractions, not concretions
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
class MySQLDatabase {
|
||||
query(sql: string) { /* ... */ }
|
||||
}
|
||||
|
||||
class UserService {
|
||||
private db = new MySQLDatabase(); // Tight coupling
|
||||
|
||||
async getUser(id: string) {
|
||||
return this.db.query(`SELECT * FROM users WHERE id = ${id}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Good (Dependency Injection):**
|
||||
```typescript
|
||||
interface Database {
|
||||
query(sql: string, params: any[]): Promise<any>;
|
||||
}
|
||||
|
||||
class MySQLDatabase implements Database {
|
||||
async query(sql: string, params: any[]) { /* ... */ }
|
||||
}
|
||||
|
||||
class PostgreSQLDatabase implements Database {
|
||||
async query(sql: string, params: any[]) { /* ... */ }
|
||||
}
|
||||
|
||||
class UserService {
|
||||
constructor(private db: Database) {} // Injected dependency
|
||||
|
||||
async getUser(id: string) {
|
||||
return this.db.query('SELECT * FROM users WHERE id = $1', [id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const db = new PostgreSQLDatabase();
|
||||
const userService = new UserService(db);
|
||||
```
|
||||
|
||||
## Design Patterns
|
||||
|
||||
### Repository Pattern
|
||||
|
||||
**Concept:** Abstraction layer between business logic and data access
|
||||
|
||||
```typescript
|
||||
// Domain entity
|
||||
class User {
|
||||
constructor(
|
||||
public id: string,
|
||||
public email: string,
|
||||
public name: string,
|
||||
) {}
|
||||
}
|
||||
|
||||
// Repository interface
|
||||
interface UserRepository {
|
||||
findById(id: string): Promise<User | null>;
|
||||
findByEmail(email: string): Promise<User | null>;
|
||||
save(user: User): Promise<void>;
|
||||
delete(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
// Implementation
|
||||
class PostgresUserRepository implements UserRepository {
|
||||
constructor(private db: Database) {}
|
||||
|
||||
async findById(id: string): Promise<User | null> {
|
||||
const row = await this.db.query('SELECT * FROM users WHERE id = $1', [id]);
|
||||
return row ? new User(row.id, row.email, row.name) : null;
|
||||
}
|
||||
|
||||
async save(user: User): Promise<void> {
|
||||
await this.db.query(
|
||||
'INSERT INTO users (id, email, name) VALUES ($1, $2, $3)',
|
||||
[user.id, user.email, user.name]
|
||||
);
|
||||
}
|
||||
|
||||
// Other methods...
|
||||
}
|
||||
|
||||
// Service layer uses repository
|
||||
class UserService {
|
||||
constructor(private userRepo: UserRepository) {}
|
||||
|
||||
async getUser(id: string) {
|
||||
return this.userRepo.findById(id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Factory Pattern
|
||||
|
||||
**Concept:** Create objects without specifying exact class
|
||||
|
||||
```typescript
|
||||
interface Notification {
|
||||
send(message: string): Promise<void>;
|
||||
}
|
||||
|
||||
class EmailNotification implements Notification {
|
||||
async send(message: string) {
|
||||
console.log(`Email sent: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
class SMSNotification implements Notification {
|
||||
async send(message: string) {
|
||||
console.log(`SMS sent: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
class PushNotification implements Notification {
|
||||
async send(message: string) {
|
||||
console.log(`Push notification sent: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationFactory {
|
||||
static create(type: 'email' | 'sms' | 'push'): Notification {
|
||||
switch (type) {
|
||||
case 'email':
|
||||
return new EmailNotification();
|
||||
case 'sms':
|
||||
return new SMSNotification();
|
||||
case 'push':
|
||||
return new PushNotification();
|
||||
default:
|
||||
throw new Error(`Unknown notification type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const notification = NotificationFactory.create('email');
|
||||
await notification.send('Hello!');
|
||||
```
|
||||
|
||||
### Decorator Pattern
|
||||
|
||||
**Concept:** Add behavior to objects dynamically
|
||||
|
||||
```typescript
|
||||
interface Coffee {
|
||||
cost(): number;
|
||||
description(): string;
|
||||
}
|
||||
|
||||
class SimpleCoffee implements Coffee {
|
||||
cost() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
description() {
|
||||
return 'Simple coffee';
|
||||
}
|
||||
}
|
||||
|
||||
class MilkDecorator implements Coffee {
|
||||
constructor(private coffee: Coffee) {}
|
||||
|
||||
cost() {
|
||||
return this.coffee.cost() + 2;
|
||||
}
|
||||
|
||||
description() {
|
||||
return `${this.coffee.description()}, milk`;
|
||||
}
|
||||
}
|
||||
|
||||
class SugarDecorator implements Coffee {
|
||||
constructor(private coffee: Coffee) {}
|
||||
|
||||
cost() {
|
||||
return this.coffee.cost() + 1;
|
||||
}
|
||||
|
||||
description() {
|
||||
return `${this.coffee.description()}, sugar`;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
let coffee: Coffee = new SimpleCoffee();
|
||||
coffee = new MilkDecorator(coffee);
|
||||
coffee = new SugarDecorator(coffee);
|
||||
|
||||
console.log(coffee.description()); // "Simple coffee, milk, sugar"
|
||||
console.log(coffee.cost()); // 13
|
||||
```
|
||||
|
||||
### Observer Pattern (Pub/Sub)
|
||||
|
||||
**Concept:** Notify multiple objects about state changes
|
||||
|
||||
```typescript
|
||||
interface Observer {
|
||||
update(event: any): void;
|
||||
}
|
||||
|
||||
class EventEmitter {
|
||||
private observers: Map<string, Observer[]> = new Map();
|
||||
|
||||
subscribe(event: string, observer: Observer) {
|
||||
if (!this.observers.has(event)) {
|
||||
this.observers.set(event, []);
|
||||
}
|
||||
this.observers.get(event)!.push(observer);
|
||||
}
|
||||
|
||||
emit(event: string, data: any) {
|
||||
const observers = this.observers.get(event) || [];
|
||||
observers.forEach(observer => observer.update(data));
|
||||
}
|
||||
}
|
||||
|
||||
// Observers
|
||||
class EmailNotifier implements Observer {
|
||||
update(event: any) {
|
||||
console.log(`Sending email about: ${event.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
class LoggerObserver implements Observer {
|
||||
update(event: any) {
|
||||
console.log(`Logging event: ${JSON.stringify(event)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const eventEmitter = new EventEmitter();
|
||||
eventEmitter.subscribe('user.created', new EmailNotifier());
|
||||
eventEmitter.subscribe('user.created', new LoggerObserver());
|
||||
|
||||
eventEmitter.emit('user.created', { type: 'user.created', userId: '123' });
|
||||
```
|
||||
|
||||
## Clean Code Practices
|
||||
|
||||
### Meaningful Names
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
function d(a: number, b: number) {
|
||||
return a * b * 0.0254;
|
||||
}
|
||||
```
|
||||
|
||||
**Good:**
|
||||
```typescript
|
||||
function calculateAreaInMeters(widthInInches: number, heightInInches: number) {
|
||||
const INCHES_TO_METERS = 0.0254;
|
||||
return widthInInches * heightInInches * INCHES_TO_METERS;
|
||||
}
|
||||
```
|
||||
|
||||
### Small Functions
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
async function processOrder(orderId: string) {
|
||||
// 200 lines of code doing everything
|
||||
// - validate order
|
||||
// - check inventory
|
||||
// - process payment
|
||||
// - update database
|
||||
// - send notifications
|
||||
// - generate invoice
|
||||
}
|
||||
```
|
||||
|
||||
**Good:**
|
||||
```typescript
|
||||
async function processOrder(orderId: string) {
|
||||
const order = await validateOrder(orderId);
|
||||
await checkInventory(order);
|
||||
const payment = await processPayment(order);
|
||||
await updateOrderStatus(orderId, 'paid');
|
||||
await sendConfirmationEmail(order);
|
||||
await generateInvoice(order, payment);
|
||||
}
|
||||
```
|
||||
|
||||
### Avoid Magic Numbers
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
if (user.age < 18) {
|
||||
throw new Error('Too young');
|
||||
}
|
||||
|
||||
setTimeout(fetchData, 86400000);
|
||||
```
|
||||
|
||||
**Good:**
|
||||
```typescript
|
||||
const MINIMUM_AGE = 18;
|
||||
if (user.age < MINIMUM_AGE) {
|
||||
throw new Error('Too young');
|
||||
}
|
||||
|
||||
const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
|
||||
setTimeout(fetchData, ONE_DAY_IN_MS);
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
try {
|
||||
const user = await db.findUser(id);
|
||||
return user;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
**Good:**
|
||||
```typescript
|
||||
try {
|
||||
const user = await db.findUser(id);
|
||||
if (!user) {
|
||||
throw new UserNotFoundError(id);
|
||||
}
|
||||
return user;
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch user', {
|
||||
userId: id,
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
throw new DatabaseError('User fetch failed', { cause: error });
|
||||
}
|
||||
```
|
||||
|
||||
### Don't Repeat Yourself (DRY)
|
||||
|
||||
**Bad:**
|
||||
```typescript
|
||||
app.post('/api/users', async (req, res) => {
|
||||
if (!req.body.email || !req.body.email.includes('@')) {
|
||||
return res.status(400).json({ error: 'Invalid email' });
|
||||
}
|
||||
// ...
|
||||
});
|
||||
|
||||
app.put('/api/users/:id', async (req, res) => {
|
||||
if (!req.body.email || !req.body.email.includes('@')) {
|
||||
return res.status(400).json({ error: 'Invalid email' });
|
||||
}
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
**Good:**
|
||||
```typescript
|
||||
function validateEmail(email: string) {
|
||||
if (!email || !email.includes('@')) {
|
||||
throw new ValidationError('Invalid email');
|
||||
}
|
||||
}
|
||||
|
||||
app.post('/api/users', async (req, res) => {
|
||||
validateEmail(req.body.email);
|
||||
// ...
|
||||
});
|
||||
|
||||
app.put('/api/users/:id', async (req, res) => {
|
||||
validateEmail(req.body.email);
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
## Code Refactoring Techniques
|
||||
|
||||
### Extract Method
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
function renderOrder(order: Order) {
|
||||
console.log('Order Details:');
|
||||
console.log(`ID: ${order.id}`);
|
||||
console.log(`Total: $${order.total}`);
|
||||
|
||||
console.log('Items:');
|
||||
order.items.forEach(item => {
|
||||
console.log(`- ${item.name}: $${item.price}`);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
function renderOrder(order: Order) {
|
||||
printOrderHeader(order);
|
||||
printOrderItems(order.items);
|
||||
}
|
||||
|
||||
function printOrderHeader(order: Order) {
|
||||
console.log('Order Details:');
|
||||
console.log(`ID: ${order.id}`);
|
||||
console.log(`Total: $${order.total}`);
|
||||
}
|
||||
|
||||
function printOrderItems(items: OrderItem[]) {
|
||||
console.log('Items:');
|
||||
items.forEach(item => {
|
||||
console.log(`- ${item.name}: $${item.price}`);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Replace Conditional with Polymorphism
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
function getShippingCost(order: Order) {
|
||||
if (order.shippingMethod === 'standard') {
|
||||
return 5;
|
||||
} else if (order.shippingMethod === 'express') {
|
||||
return 15;
|
||||
} else if (order.shippingMethod === 'overnight') {
|
||||
return 30;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
interface ShippingMethod {
|
||||
getCost(): number;
|
||||
}
|
||||
|
||||
class StandardShipping implements ShippingMethod {
|
||||
getCost() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
|
||||
class ExpressShipping implements ShippingMethod {
|
||||
getCost() {
|
||||
return 15;
|
||||
}
|
||||
}
|
||||
|
||||
class OvernightShipping implements ShippingMethod {
|
||||
getCost() {
|
||||
return 30;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Code Quality Checklist
|
||||
|
||||
- [ ] SOLID principles applied
|
||||
- [ ] Functions are small (< 20 lines ideal)
|
||||
- [ ] Meaningful variable/function names
|
||||
- [ ] No magic numbers (use constants)
|
||||
- [ ] Proper error handling (no silent failures)
|
||||
- [ ] DRY (no code duplication)
|
||||
- [ ] Comments explain "why", not "what"
|
||||
- [ ] Design patterns used appropriately
|
||||
- [ ] Dependency injection for testability
|
||||
- [ ] Code is readable (readable > clever)
|
||||
|
||||
## Resources
|
||||
|
||||
- **Clean Code (Book):** Robert C. Martin
|
||||
- **Refactoring (Book):** Martin Fowler
|
||||
- **Design Patterns:** https://refactoring.guru/design-patterns
|
||||
- **SOLID Principles:** https://en.wikipedia.org/wiki/SOLID
|
||||
Reference in New Issue
Block a user