660 lines
13 KiB
Markdown
660 lines
13 KiB
Markdown
# 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
|