Análise prática de armadilhas frequentes, com exemplos claros e caminhos de correção para manter seu código sustentável e fácil de evoluir.
1) Violação do SRP (Single Responsibility Principle)
Quando uma classe acumula responsabilidades distintas — validação, persistência, logging ou lógica de negócio — o código vira uma teia difícil de manter. Em cenários reais, mudanças em uma responsabilidade costumam impactar as outras, elevando o custo de evolução e introduzindo regressões.
- Divide responsabilidades claras em componentes independentes.
- Procure manter a lógica de negócio separada de IO, validações e acessos a dados.
- Favoreça injeção de dependências para compor comportamentos sem acoplar classes concretas.
// Mau: violando SRP
class UserProfile {
constructor(private user: User) {}
validate(user: User): boolean {
// validações
}
save(user: User) {
// persistência
}
log(user: User, action: string) {
// log de ação
}
}
// Bom: SRP aplicado
class UserValidator {
validate(user: User): boolean { /* validações */ return true; }
}
class UserRepository {
save(user: User): void { /* persistência */ }
}
class UserService {
constructor(private repo: UserRepository, private validator: UserValidator) {}
create(user: User) {
if (this.validator.validate(user)) {
this.repo.save(user);
}
}
}
2) Falha no OCP (Open/Closed Principle) ao evoluir o código
Ao introduzir novas variações, otimizar para extensibilidade sem alterar código existente costuma ser mais seguro. Um antipadrão comum é usar estruturas condicionais para tratar diferentes tipos, o que obriga a modificar funções toda vez que surgem novos tipos.
- Escolha abstrações que permitam estender comportamento sem modificar código existente.
- Prefira composição por meio de interfaces e polimorfismo sobre estruturas condicionais extensíveis.
// Mau: usa switch para acomodar tipos
function area(shape: Shape) {
switch (shape.kind) {
case 'circle': return Math.PI * shape.r * shape.r;
case 'square': return shape.s * shape.s;
}
}
// Bom: polimorfismo respeita OCP
interface Shape { area(): number; }
class Circle implements Shape { constructor(public r: number){} area(){ return Math.PI * this.r * this.r; } }
class Square implements Shape { constructor(public s: number){} area(){ return this.s * this.s; } }
function totalArea(shapes: Shape[]) { return shapes.reduce((a, s) => a + s.area(), 0); }
3) Violação do LSP (Liskov Substitution Principle)
O LSP defende que objetos de uma classe derivada devem poder substituir objetos da classe base sem alterar o comportamento esperado. Pode ocorrer quando o subtipo altera contratos do método, invariantes ou pré-condições.
- Evite herdários que alterem invariantes da superclasse.
- Quando necessário, prefira composição ou interfaces simples ao invés de extensões profundas.
// Mau: Square viola LSP
class Rectangle {
protected w: number; protected h: number;
constructor(w: number, h: number) { this.w = w; this.h = h; }
setWidth(w: number) { this.w = w; }
setHeight(h: number) { this.h = h; }
getArea() { return this.w * this.h; }
}
class Square extends Rectangle {
setWidth(w: number) { this.w = this.h = w; }
setHeight(h: number) { this.h = this.w = h; }
}
function printArea(r: Rectangle) {
r.setWidth(5); r.setHeight(4);
console.log(r.getArea());
}
// Bom: respeita LSP ao evitar herança problematica
class Rectangle {
constructor(public w: number, public h: number) {}
getArea() { return this.w * this.h; }
}
class Square {
constructor(public side: number) {}
getArea() { return this.side * this.side; }
}
function printArea(shape: { getArea(): number }) {
console.log(shape.getArea());
}
4) Violação do ISP (Interface Segregation Principle)
Interfaces grandes forçam implementações a trazerem métodos não usados, tornando as classes dependentes de comportamentos desnecessários. ISP incentiva interfaces menores, mais específicas e coesas.
- Evite interfaces “gigantes” que forçam implementações irrelevantes.
- Ofereça várias interfaces focadas para diferentes papéis no sistema.
// Mau: interface com responsabilidades misturadas
interface IWorker {
code(): void;
test(): void;
design(): void;
deploy(): void;
}
class Dev implements IWorker {
code() {}
test() {}
design() {}
deploy() {}
}
// Bom: interfaces segregadas
interface ICode { code(): void; }
interface ITest { test(): void; }
interface IDesign { design(): void; }
interface IDeploy { deploy(): void; }
class Developer implements ICode, ITest { code() {} test() {} }
class Architect implements IDesign, IDeploy, ICode { design() {} deploy() {} code() {} }
Gostou do conteúdo? Continue explorando o universo SOLID
Para aprofundar, confira outros posts que ajudam a consolidar boas práticas e a evoluir seu código de forma consciente:
Sou Apaixonado pela programação e estou trilhando o caminho de ter cada diz mais conhecimento e trazer toda minha experiência vinda do Design para a programação resultando em layouts incríveis e idéias inovadoras! Conecte-se Comigo!