Os 5 pilares do código limpo SOLID Principles
Transforme seu código TypeScript em uma obra de arte com os princípios SOLID
5 regras que todo dev sênior domina SOLID Principles
Pare de escrever código que quebra. Aprenda os princípios que separam devs júnior de sênior.
Por que isso é importante
Código que não quebra
SOLID previne 80% dos bugs mais comuns. Seu código funciona e continua funcionando.
Desenvolvimento 3x mais rápido
Menos tempo debugando, mais tempo criando. Features novas sem medo.
Carreira acelerada
SOLID é requisito para dev sênior. Diferencial em entrevistas técnicas.
Trabalho em equipe
Código que outros devs entendem. Onboarding sem dor de cabeça.
Os 5 Princípios SOLID
Cada letra representa um princípio fundamental do código limpo.
Single Responsibility Principle
Uma classe, uma responsabilidade
❌ Violando o princípio:
class User {
constructor(public name: string, public email: string) {}
// Responsabilidade 1: Validação
validateEmail(): boolean {
return /^[^s@]+@[^s@]+.[^s@]+$/.test(this.email);
}
// Responsabilidade 2: Persistência
save(): void {
// Salva no banco de dados
console.log('Salvando usuário...');
}
// Responsabilidade 3: Notificação
sendWelcomeEmail(): void {
// Envia email de boas-vindas
console.log('Enviando email...');
}
// Responsabilidade 4: Relatórios
generateReport(): string {
return `Relatório do usuário ${this.name}`;
}
}✅ Respeitando o princípio:
// Uma responsabilidade por classe
class User {
constructor(public name: string, public email: string) {}
}
class EmailValidator {
validate(email: string): boolean {
return /^[^s@]+@[^s@]+.[^s@]+$/.test(email);
}
}
class UserRepository {
save(user: User): void {
console.log('Salvando usuário...');
}
}
class EmailService {
sendWelcomeEmail(user: User): void {
console.log(`Enviando email para ${user.email}`);
}
}
class UserReportGenerator {
generate(user: User): string {
return `Relatório do usuário ${user.name}`;
}
}🎯 Resultado: Cada classe tem um motivo para mudar. Testes isolados e código mais limpo.
Open-Closed Principle
Aberto para extensão, fechado para modificação
❌ Violando o princípio:
function calculateDiscount(customer: Customer): number {
switch (customer.type) {
case 'regular':
return customer.total * 0.05;
case 'premium':
return customer.total * 0.1;
case 'vip':
return customer.total * 0.2;
// Precisa modificar para adicionar novo tipo
default:
return 0;
}
}✅ Respeitando o princípio:
abstract class Customer {
constructor(public total: number) {}
abstract getDiscount(): number;
}
class RegularCustomer extends Customer {
getDiscount(): number {
return this.total * 0.05;
}
}
class PremiumCustomer extends Customer {
getDiscount(): number {
return this.total * 0.1;
}
}
// Novo tipo SEM modificar código existente
class VIPCustomer extends Customer {
getDiscount(): number {
return this.total * 0.2;
}
}🎯 Resultado: Novas funcionalidades sem quebrar o que já funciona. Zero regressões.
Liskov Substitution Principle
Subclasses devem ser substituíveis por suas classes base
❌ Violando o princípio:
class Bird {
fly(): void {
console.log('Voando...');
}
}
class Penguin extends Bird {
fly(): void {
// Pinguim não voa!
throw new Error('Pinguins não voam!');
}
}
// Quebra quando usa Penguin
function makeBirdFly(bird: Bird): void {
bird.fly(); // Pode dar erro!
}✅ Respeitando o princípio:
abstract class Bird {
abstract move(): void;
}
class FlyingBird extends Bird {
move(): void {
console.log('Voando...');
}
}
class SwimmingBird extends Bird {
move(): void {
console.log('Nadando...');
}
}
class Eagle extends FlyingBird {}
class Penguin extends SwimmingBird {}
// Funciona com qualquer Bird
function makeBirdMove(bird: Bird): void {
bird.move(); // Sempre funciona!
}🎯 Resultado: Polimorfismo que funciona. Sem surpresas desagradáveis.
Interface Segregation Principle
Muitas interfaces específicas são melhores que uma geral
❌ Violando o princípio:
// Interface muito gorda
interface Worker {
work(): void;
eat(): void;
sleep(): void;
code(): void;
design(): void;
manage(): void;
}
// Robot precisa implementar tudo!
class Robot implements Worker {
work(): void { console.log('Trabalhando...'); }
eat(): void { throw new Error('Robôs não comem!'); }
sleep(): void { throw new Error('Robôs não dormem!'); }
code(): void { throw new Error('Robôs não programam!'); }
design(): void { throw new Error('Robôs não fazem design!'); }
manage(): void { throw new Error('Robôs não gerenciam!'); }
}✅ Respeitando o princípio:
// Interfaces específicas
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
interface Programmable {
code(): void;
}
// Cada classe implementa só o que precisa
class Robot implements Workable {
work(): void {
console.log('Robô trabalhando...');
}
}
class Developer implements Workable, Eatable, Sleepable, Programmable {
work(): void { console.log('Desenvolvendo...'); }
eat(): void { console.log('Comendo...'); }
sleep(): void { console.log('Dormindo...'); }
code(): void { console.log('Programando...'); }
}🎯 Resultado: Classes implementam apenas o que realmente usam. Sem métodos vazios.
Dependency Inversion Principle
Dependa de abstrações, não de implementações
❌ Violando o princípio:
class EmailService {
send(message: string): void {
console.log(`Enviando email: ${message}`);
}
}
// Acoplado diretamente ao EmailService
class NotificationManager {
private emailService = new EmailService();
notify(message: string): void {
// Só pode enviar email!
this.emailService.send(message);
}
}✅ Respeitando o princípio:
// Abstração
interface NotificationService {
send(message: string): void;
}
class EmailService implements NotificationService {
send(message: string): void {
console.log(`Email: ${message}`);
}
}
class SMSService implements NotificationService {
send(message: string): void {
console.log(`SMS: ${message}`);
}
}
// Depende da abstração
class NotificationManager {
constructor(private service: NotificationService) {}
notify(message: string): void {
this.service.send(message);
}
}
// Uso flexível
const emailManager = new NotificationManager(new EmailService());
const smsManager = new NotificationManager(new SMSService());🎯 Resultado: Código flexível e testável. Fácil de trocar implementações.
Exemplo Prático: Sistema de Pedidos
Veja todos os princípios SOLID trabalhando juntos.
🛒 Sistema de E-commerce SOLID
1. Entidades e Interfaces (S + I):
// S: Cada classe tem uma responsabilidade
class Product {
constructor(
public id: string,
public name: string,
public price: number
) {}
}
class Order {
constructor(
public id: string,
public customerId: string,
public items: OrderItem[] = []
) {}
getTotal(): number {
return this.items.reduce((sum, item) => sum + item.getSubtotal(), 0);
}
}
class OrderItem {
constructor(
public product: Product,
public quantity: number
) {}
getSubtotal(): number {
return this.product.price * this.quantity;
}
}
// I: Interfaces específicas
interface OrderRepository {
save(order: Order): Promise<void>;
findById(id: string): Promise<Order | null>;
}
interface PaymentProcessor {
process(amount: number, method: string): Promise<boolean>;
}
interface NotificationSender {
send(message: string, recipient: string): Promise<void>;
}2. Processamento de Desconto (O + L):
// O: Aberto para extensão, fechado para modificação
abstract class DiscountCalculator {
abstract calculate(order: Order): number;
}
// L: Substituição de Liskov - todas funcionam igual
class NoDiscount extends DiscountCalculator {
calculate(order: Order): number {
return 0;
}
}
class PercentageDiscount extends DiscountCalculator {
constructor(private percentage: number) {
super();
}
calculate(order: Order): number {
return order.getTotal() * (this.percentage / 100);
}
}
class FixedAmountDiscount extends DiscountCalculator {
constructor(private amount: number) {
super();
}
calculate(order: Order): number {
return Math.min(this.amount, order.getTotal());
}
}
class BuyTwoGetOneDiscount extends DiscountCalculator {
calculate(order: Order): number {
let discount = 0;
order.items.forEach(item => {
const freeItems = Math.floor(item.quantity / 3);
discount += freeItems * item.product.price;
});
return discount;
}
}3. Serviço Principal (D):
// D: Dependency Inversion - depende de abstrações
class OrderService {
constructor(
private orderRepository: OrderRepository,
private paymentProcessor: PaymentProcessor,
private notificationSender: NotificationSender,
private discountCalculator: DiscountCalculator
) {}
async processOrder(order: Order, paymentMethod: string): Promise<boolean> {
try {
// Calcula desconto
const discount = this.discountCalculator.calculate(order);
const finalAmount = order.getTotal() - discount;
// Processa pagamento
const paymentSuccess = await this.paymentProcessor.process(
finalAmount,
paymentMethod
);
if (!paymentSuccess) {
throw new Error('Falha no pagamento');
}
// Salva pedido
await this.orderRepository.save(order);
// Envia notificação
await this.notificationSender.send(
`Pedido ${order.id} confirmado! Total: R$ ${finalAmount}`,
order.customerId
);
return true;
} catch (error) {
console.error('Erro ao processar pedido:', error);
return false;
}
}
}4. Implementações Concretas:
// Implementações específicas
class DatabaseOrderRepository implements OrderRepository {
async save(order: Order): Promise<void> {
console.log(`Salvando pedido ${order.id} no banco`);
}
async findById(id: string): Promise<Order | null> {
console.log(`Buscando pedido ${id}`);
return null; // Simulação
}
}
class StripePaymentProcessor implements PaymentProcessor {
async process(amount: number, method: string): Promise<boolean> {
console.log(`Processando R$ ${amount} via ${method}`);
return true; // Simulação
}
}
class EmailNotificationSender implements NotificationSender {
async send(message: string, recipient: string): Promise<void> {
console.log(`Email para ${recipient}: ${message}`);
}
}
// Uso do sistema
const orderService = new OrderService(
new DatabaseOrderRepository(),
new StripePaymentProcessor(),
new EmailNotificationSender(),
new PercentageDiscount(10) // 10% de desconto
);
const order = new Order('001', 'customer123', [
new OrderItem(new Product('p1', 'Notebook', 2000), 1),
new OrderItem(new Product('p2', 'Mouse', 50), 2)
]);
await orderService.processOrder(order, 'credit_card');🎯 Resultado: Sistema flexível, testável e extensível. Cada princípio SOLID em ação!
Checklist SOLID
Como aplicar os princípios SOLID no seu código.
✅ Faça Isso
- Uma responsabilidade por classe
- Use herança e polimorfismo
- Subclasses substituíveis
- Interfaces específicas
- Dependency injection
- Testes unitários isolados
❌ Evite Isso
- Classes com múltiplas responsabilidades
- Switch/case gigantes
- Herança que quebra comportamento
- Interfaces muito grandes
- Acoplamento forte
- Instanciação direta de dependências
🎯 Resumo SOLID
"Clean code is not written by following a set of rules. Clean code is written by programmers who care." - Robert C. Martin