← Voltar para a página inicial

Princípios e Padrões de Engenharia de Software

Guia técnico único e direto: dos princípios fundamentais (KISS, DRY, YAGNI, SOLID) aos design patterns e padrões de arquitetura (MVC, MVVM, Clean Architecture), com métricas de qualidade, checklist de revisão, exemplos de código e armadilhas comuns — tudo para construir sistemas manuteníveis, confiáveis, seguros e eficientes.

1) O que são princípios e padrões?

São soluções consagradas para problemas que se repetem ao escrever software. Não são bibliotecas que você instala, e sim receitas de organização — formas testadas de estruturar código para que ele seja fácil de ler, testar, mudar e reaproveitar. Existem três grandes níveis, do mais geral ao mais concreto:

2) Fundamentos: KISS, DRY e YAGNI

Antes de qualquer padrão mais sofisticado, três princípios simples evitam boa parte dos problemas de código:

3) SOLID — os 5 princípios da orientação a objetos

SOLID é um acrônimo de cinco princípios (popularizados por Robert C. Martin) que tornam o código flexível, desacoplado e testável. A ideia central: mudanças futuras devem ser baratas.

Letra Princípio Em uma frase
S Single Responsibility (Responsabilidade Única) Uma classe deve ter apenas um motivo para mudar.
O Open/Closed (Aberto/Fechado) Aberto para extensão, fechado para modificação.
L Liskov Substitution (Substituição de Liskov) Subclasses devem poder substituir a classe-mãe sem quebrar nada.
I Interface Segregation (Segregação de Interface) Muitas interfaces específicas são melhores que uma genérica.
D Dependency Inversion (Inversão de Dependência) Dependa de abstrações, não de implementações concretas.

Exemplo: Responsabilidade Única (SRP)

Antes — a classe faz coisas demais (calcula, salva e envia e-mail):

class Pedido:
    def calcular_total(self): ...
    def salvar_no_banco(self): ...   # responsabilidade de persistência
    def enviar_email(self): ...      # responsabilidade de notificação

Depois — cada classe tem um único motivo para mudar:

class Pedido:
    def calcular_total(self): ...

class RepositorioPedido:
    def salvar(self, pedido): ...

class Notificador:
    def enviar_email(self, pedido): ...

Exemplo: Inversão de Dependência (DIP)

O código de alto nível depende de uma abstração, então trocar o serviço de envio não exige mexer na lógica de negócio:

class CanalDeEnvio:        # abstração
    def enviar(self, msg): raise NotImplementedError

class Email(CanalDeEnvio):
    def enviar(self, msg): print("e-mail:", msg)

class SMS(CanalDeEnvio):
    def enviar(self, msg): print("sms:", msg)

class Notificador:
    def __init__(self, canal: CanalDeEnvio):  # recebe a abstração
        self.canal = canal
    def avisar(self, msg):
        self.canal.enviar(msg)

Notificador(Email()).avisar("Pedido confirmado")
Notificador(SMS()).avisar("Pedido confirmado")

4) Design Patterns (Gang of Four)

Os 23 padrões clássicos do livro "Design Patterns" (1994) se dividem em três famílias. Você não precisa decorar todos — conheça os mais usados e aplique quando o problema aparecer, nunca de forma forçada.

Criacionais — como objetos são criados

Factory Method Abstract Factory Builder Singleton Prototype

Factory: centraliza a criação de objetos para que o resto do código não precise saber qual classe concreta está sendo instanciada.

def criar_transporte(tipo):
    fabrica = {"carro": Carro, "navio": Navio, "aviao": Aviao}
    if tipo not in fabrica:
        raise ValueError("Transporte inválido")
    return fabrica[tipo]()   # quem chama não conhece as classes concretas

Estruturais — como objetos se compõem

Adapter Decorator Facade Proxy Composite

Adapter: faz duas interfaces incompatíveis trabalharem juntas — como um adaptador de tomada. Decorator: adiciona comportamento a um objeto sem alterar sua classe.

Comportamentais — como objetos colaboram

Strategy Observer Command State Iterator

Strategy: permite trocar um algoritmo em tempo de execução (ótimo aliado do princípio Open/Closed):

class Frete:
    def __init__(self, estrategia):
        self.estrategia = estrategia
    def calcular(self, peso):
        return self.estrategia(peso)

normal  = Frete(lambda p: p * 2)
expresso = Frete(lambda p: p * 5)
print(normal.calcular(10))    # 20
print(expresso.calcular(10))  # 50

Observer: quando um objeto muda de estado, todos os "inscritos" são avisados automaticamente — é a base de sistemas de eventos e da reatividade (ex.: React, Vue).

Padrões de camada: Repository e Service

Muito usados em aplicações com banco de dados, esses dois padrões aplicam SRP separando responsabilidades em camadas e abrem caminho para os padrões de arquitetura da próxima seção:

5) Padrões de arquitetura: MVC, MVVM e Clean Architecture

Enquanto os design patterns organizam classes, os padrões de arquitetura organizam o sistema inteiro, normalmente separando responsabilidades em camadas.

MVC — Model, View, Controller

O padrão mais conhecido para aplicações web e desktop. Divide a aplicação em três papéis:

Usuário → Controller → Model (busca/atualiza dados)
                ↓
              View (renderiza o resultado)

Usado por frameworks como Spring (Java), Laravel (PHP), Ruby on Rails e ASP.NET.

MVVM — Model, View, ViewModel

Variação do MVC pensada para interfaces reativas. O ViewModel expõe os dados já prontos para a tela e mantém a View sincronizada com o Model por meio de data binding. Comum em apps com Angular, Vue, WPF e SwiftUI.

Clean Architecture / Hexagonal

Organiza o código em círculos concêntricos: a regra de negócio fica no centro e não conhece banco de dados, framework nem interface. As dependências sempre apontam de fora para dentro, o que mantém o núcleo testável e independente de tecnologia.

[ Frameworks / UI / Banco ]  →  [ Adaptadores ]  →  [ Casos de uso ]  →  [ Entidades ]
        (externo, troca fácil)                                          (núcleo estável)

Layered Architecture (Arquitetura em Camadas)

Organiza o código em camadas horizontais — geralmente apresentação, negócio/domínio e dados/persistência — em que cada camada só conhece a camada imediatamente abaixo. É a base de muitos sistemas corporativos e combina bem com Repository e Service.

Microsserviços

Divide o sistema em serviços independentes, cada um com seu próprio banco e ciclo de deploy. Ganha-se escalabilidade e times autônomos, mas paga-se com complexidade operacional — é essencial controlar acoplamento e contratos (APIs) entre serviços, sob pena de trocar "código espaguete" por "arquitetura espaguete distribuída".

6) Quadro comparativo: quando usar cada um

Padrão Problema que resolve Use quando…
SOLID Código rígido e difícil de mudar Sempre, como base de qualquer projeto OO.
Factory Criação de objetos espalhada e acoplada Há várias classes concretas para um mesmo tipo.
Strategy Muitos if/else escolhendo algoritmos O comportamento precisa variar em tempo de execução.
Observer Objetos que precisam reagir a mudanças Eventos, notificações, UI reativa.
Repository Lógica de acesso a dados misturada ao negócio Quer isolar o banco do restante do código.
MVC / MVVM Interface e regra de negócio embaralhadas Aplicações com tela (web, mobile, desktop).
Clean Architecture Dependência forte de framework/banco Sistemas grandes e de vida longa.
Layered / Microsserviços Sistema monolítico difícil de escalar ou times pisando uns nos outros Organização em camadas (layered) ou necessidade de deploy independente (microsserviços).

7) Impacto prático dos princípios

8) Métricas para acompanhar qualidade

Princípio Benefício Métricas/Sinais Ferramentas
KISS Legibilidade e menor risco de bugs Complexidade ciclomática baixa (ideal < 10) ESLint, Pylint, SonarQube
DRY Menos retrabalho e inconsistência % de duplicação e code smells SonarQube, PMD
YAGNI Entrega rápida de valor Código morto, funcionalidades não usadas Revisão de backlog, métricas de uso
SOLID Arquitetura extensível e testável Coesão/acoplamento, classes grandes, mudanças em cascata SonarQube, ArchUnit, revisão arquitetural
TDD e testes Confiabilidade e prevenção de regressões Cobertura, taxa de falha, tempo da suíte pytest, JUnit, Jest, CI/CD
Secure by Design Redução de vulnerabilidades Achados SAST/DAST, falhas OWASP Top 10 OWASP ZAP, CodeQL, Dependency Check

9) Checklist rápido de revisão

10) Exemplos de código: modularidade e segurança

Python (modularidade + tratamento de erro)

def calcular_produto(valor):
    return valor * 0.9

def processar_pedido(tipo, valor):
    calculadoras = {"produto": calcular_produto, "servico": lambda v: v}
    if tipo not in calculadoras:
        raise ValueError("Tipo inválido")
    return calculadoras[tipo](valor)

Java (segurança contra SQL Injection)

PreparedStatement stmt = conn.prepareStatement(
    "SELECT * FROM users WHERE login = ?"
);
stmt.setString(1, user);
ResultSet rs = stmt.executeQuery();

11) Armadilhas comuns

12) Recursos para se aprofundar

Resumo: domine primeiro os princípios (KISS, DRY, YAGNI, SOLID), meça a qualidade com métricas objetivas, reconheça os design patterns quando o problema realmente aparecer e escolha a arquitetura adequada ao tamanho do sistema. Padrões são ferramentas, não objetivos — o resultado final deve ser sempre código simples, modular, testável, seguro e fácil de mudar.

← Voltar para a página inicial