Erros Comuns em SOLID que Você Deve Evitar: Guia Prático para Desenvolvedores

Erros Comuns em SOLID que Você Deve Evitar: Guia Prático para Desenvolvedores





Erros comuns em SOLID que você deve evitar



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: