SOLID principles and TDD enforcement rules. Reference for maintainable software design. Used for code quality analysis, refactoring planning, and architecture verification.
/plugin marketplace add chkim-su/serena-refactor-marketplace/plugin install serena-refactor@serena-refactor-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Design for change isolation. Predict what will change and strictly limit the propagation scope of those changes.
Every class, module, and function must have exactly one reason to change.
Prohibited:
Required:
Example - Violation:
// ❌ SRP Violation: Handles business logic, DB, and email
class UserService {
async createUser(data: UserData) {
// Validation logic
if (!data.email.includes('@')) throw new Error('Invalid email');
// DB save
await db.query('INSERT INTO users...');
// Email sending
await smtp.send({ to: data.email, subject: 'Welcome!' });
// Logging
console.log('User created:', data.email);
}
}
Example - Fixed:
// ✓ SRP Compliant: Each responsibility separated
class UserValidator {
validate(data: UserData): ValidationResult { ... }
}
class UserRepository {
async save(user: User): Promise<void> { ... }
}
class WelcomeEmailSender {
async send(user: User): Promise<void> { ... }
}
class UserCreationUseCase {
constructor(
private validator: UserValidator,
private repo: UserRepository,
private emailer: WelcomeEmailSender
) {}
async execute(data: UserData): Promise<User> {
this.validator.validate(data);
const user = await this.repo.save(new User(data));
await this.emailer.send(user);
return user;
}
}
Systems must be open for extension, closed for modification.
Prohibited:
if/switch chains that grow with types or enumsRequired:
Example - Violation:
// ❌ OCP Violation: Adding new payment requires modifying switch
function processPayment(type: string, amount: number) {
switch (type) {
case 'credit': return processCreditCard(amount);
case 'paypal': return processPaypal(amount);
case 'crypto': return processCrypto(amount);
// Adding new type requires case here...
}
}
Example - Fixed:
// ✓ OCP Compliant: New payment just implements interface
interface PaymentProcessor {
process(amount: number): Promise<PaymentResult>;
}
class CreditCardProcessor implements PaymentProcessor { ... }
class PaypalProcessor implements PaymentProcessor { ... }
class CryptoProcessor implements PaymentProcessor { ... }
// New method: Just add implementation without modifying existing code
class ApplePayProcessor implements PaymentProcessor { ... }
class PaymentService {
constructor(private processors: Map<string, PaymentProcessor>) {}
async process(type: string, amount: number) {
return this.processors.get(type)?.process(amount);
}
}
Subtypes must be completely substitutable for their base types.
Prohibited:
instanceof checks in calling codeRequired:
Example - Violation:
// ❌ LSP Violation: Square breaks Rectangle contract
class Rectangle {
constructor(public width: number, public height: number) {}
setWidth(w: number) { this.width = w; }
setHeight(h: number) { this.height = h; }
area() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(w: number) { this.width = this.height = w; } // Contract violation!
setHeight(h: number) { this.width = this.height = h; } // Contract violation!
}
// Client code doesn't work as expected
function resize(rect: Rectangle) {
rect.setWidth(5);
rect.setHeight(10);
console.log(rect.area()); // Rectangle: 50, Square: 100 (!)
}
Example - Fixed:
// ✓ LSP Compliant: Separated with common interface
interface Shape {
area(): number;
}
class Rectangle implements Shape {
constructor(public width: number, public height: number) {}
area() { return this.width * this.height; }
}
class Square implements Shape {
constructor(public side: number) {}
area() { return this.side * this.side; }
}
Interfaces must be designed from the client's perspective.
Prohibited:
Required:
Example - Violation:
// ❌ ISP Violation: Fat interface
interface Worker {
work(): void;
eat(): void;
sleep(): void;
attendMeeting(): void;
writeReport(): void;
}
// Robot doesn't need eat, sleep but forced to implement
class Robot implements Worker {
work() { ... }
eat() { throw new Error('Not applicable'); } // Meaningless implementation
sleep() { throw new Error('Not applicable'); }
...
}
Example - Fixed:
// ✓ ISP Compliant: Separated by role
interface Workable {
work(): void;
}
interface Feedable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
class Human implements Workable, Feedable, Sleepable { ... }
class Robot implements Workable { ... } // Only implements what's needed
High-level business logic must not depend on low-level details.
Prohibited:
new keyword for infrastructure classes in business logicRequired:
Example - Violation:
// ❌ DIP Violation: Business logic directly depends on concrete classes
class OrderService {
private db = new MySQLConnection(); // Direct creation!
private mailer = new SendGridClient(); // Direct creation!
async createOrder(data: OrderData) {
await this.db.query('INSERT INTO orders...');
await this.mailer.send(data.customerEmail, 'Order confirmed');
}
}
Example - Fixed:
// ✓ DIP Compliant: Depends on abstractions, injected
interface OrderRepository {
save(order: Order): Promise<void>;
}
interface NotificationService {
notify(recipient: string, message: string): Promise<void>;
}
class OrderService {
constructor(
private repo: OrderRepository, // Interface injected
private notifier: NotificationService // Interface injected
) {}
async createOrder(data: OrderData) {
const order = new Order(data);
await this.repo.save(order);
await this.notifier.notify(data.customerEmail, 'Order confirmed');
}
}
// Implementations injected from outside
const service = new OrderService(
new MySQLOrderRepository(),
new SendGridNotifier()
);
| Signal | Violation |
|---|---|
| Excessive mocking | SRP violation |
| Need to test private methods | Wrong boundaries |
| DB/Network needed for unit tests | DIP violation |
Example - Testable Design:
// ✓ Testable: Dependencies can be mocked via injection
describe('OrderService', () => {
it('should save order and notify customer', async () => {
const mockRepo = { save: jest.fn() };
const mockNotifier = { notify: jest.fn() };
const service = new OrderService(mockRepo, mockNotifier);
await service.createOrder({ customerEmail: 'test@test.com' });
expect(mockRepo.save).toHaveBeenCalled();
expect(mockNotifier.notify).toHaveBeenCalledWith('test@test.com', expect.any(String));
});
});
Repository = Collection abstraction, not persistence mechanism
Prohibited:
Required:
find, save, exists)Example:
// ✓ Domain-centric interface
interface UserRepository {
findById(id: UserId): Promise<User | null>;
findByEmail(email: Email): Promise<User | null>;
save(user: User): Promise<void>;
exists(id: UserId): Promise<boolean>;
}
// In-memory implementation for tests
class InMemoryUserRepository implements UserRepository {
private users: Map<string, User> = new Map();
async findById(id: UserId) {
return this.users.get(id.value) ?? null;
}
// ...
}
Build robust backtesting systems for trading strategies with proper handling of look-ahead bias, survivorship bias, and transaction costs. Use when developing trading algorithms, validating strategies, or building backtesting infrastructure.