Files
2026-04-12 01:06:31 +07:00

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