Aula 4 • Projeto 9 • Sistemas de Informação
Design Orientado a Objetos & TDD

Do Diagrama de Classes à Implementação Limpa com Testes Unitários.

📐 Design Patterns 📊 UML Avançado 🧪 TDD Prático ♻️ Refatoração
Prof. Afonso Brandão • 2 horas • Fevereiro 2025
📋 Agenda da Aula

1. Fundamentos e Design

  • Deep Dive nos 4 Pilares da OO
  • Herança vs Composição (O grande dilema)
  • Mapeamento UML -> Código (C#)
  • Princípios SOLID (S e O)

2. Qualidade e Prática

  • Filosofia do TDD: Design > Teste
  • O Ciclo Red-Green-Refactor detalhado
  • Anti-patterns de testes
  • IA Agents: gerar código, validar outputs e falhas
🌉 O Elo Perdido: Dados x Comportamento
Conectando a aula de Banco de Dados com POO

Aula Passada (Dados)

Focamos em ESTADO.

  • Tabelas, Colunas, Tipos
  • Normalização (Evitar redundância)
  • Modelo Anêmico (Só dados, sem lógica)
struct Aluno { string nome; }

Hoje (Objetos)

Focamos em COMPORTAMENTO.

  • Classes, Métodos, Contratos
  • Encapsulamento (Esconder dados)
  • Modelo Rico (Dados + Lógica juntos)
class Aluno { Matricular() { ... } }

"Objetos são dados com comportamento; Closures são comportamento com dados."

🛡️ Pilar 1: Encapsulamento Real
Tell, Don't Ask

Encapsulamento não é (apenas) criar getters e setters para tudo.

É sobre proteger invariantes e esconder detalhes de implementação.

❌ Jeito Errado (Anêmico)

var conta = new Conta(); // Lógica vaza para fora da classe if (conta.Saldo >= 100) { conta.Saldo -= 100; }

✅ Jeito Certo (Tell, Don't Ask)

var conta = new Conta(); // Pede para o objeto fazer o trabalho try { conta.Sacar(100); } catch (SaldoInsuficienteException e) { ... }
Lei de Demeter: "Não fale com estranhos". Evite correntes como pedido.Cliente.Endereco.Cep.
👾 Encapsulamento: O Tamagotchi
Você não opera o estômago do seu pet!
👾

A Regra de Ouro:

Num Tamagotchi, você não pode simplesmente dizer: bichinho.Fome = 0;

Isso violaria as regras internas (invariantes). E se a fome ficar negativa? E se ele morrer de tanto comer?

// ❌ Errado (Cirurgia a céu aberto) bichinho.Fome -= 10; bichinho.Felicidade += 5; // ✅ Certo (Interação comportamental) bichinho.Alimentar(); // O método sabe como reduzir a fome // e aumentar a felicidade corretamente.
🏗️ Herança vs Composição
O Trade-off Clássico

Herança (É um)

  • ✅ Reuso de código fácil.
  • ❌ Alto acoplamento (mudou o pai, quebrou o filho).
  • ❌ Herança é estática (definida em tempo de compilação).
  • ⚠️ Violação fácil de Liskov (LSP).
class PatoBorracha : Pato { override Voar() { throw new Error(); } } // Quebrou o contrato!

Composição (Tem um)

  • ✅ Baixo acoplamento.
  • ✅ Flexibilidade dinâmica (trocar comportamento em runtime).
  • ✅ Mais fácil de testar (Mocks).
class Pato { private IVoador _comportamentoVoo; public Pato(IVoador v) { _comportamentoVoo = v; } }

"Favoreça a composição sobre a herança." - Effective Java

🎭 Polimorfismo & Interfaces
Programar para Interfaces, não Implementações

O poder de tratar objetos diferentes da mesma maneira.

Dependency Inversion Principle (DIP):

Módulos de alto nível não devem depender de módulos de baixo nível. Ambos devem depender de abstrações.

// Acoplado (Ruim) class PagamentoService { private PayPal _paypal = new PayPal(); // Dependência concreta! } // Desacoplado (Bom) - Polimorfismo class PagamentoService { private IPagamento _gateway; // Injeção de Dependência public PagamentoService(IPagamento gateway) { _gateway = gateway; } }
⚔️ Polimorfismo: Batalha RPG
O botão 'X' do controle

O jogo não sabe qual personagem você escolheu. Ele apenas sabe que é um Personagem.

O Controle (Client)

Quando você aperta X, o controle envia o comando Atacar().

Class Guerreiro : Personagem

Atacar() ->
"Desfere golpe de espada!"

Class Mago : Personagem

Atacar() ->
"Lança bola de fogo!"

Personagem p = new Mago(); // ou new Guerreiro(); p.Atacar(); // O comportamento muda conforme o objeto real!
📐 UML: Anatomia da Classe
Lendo o mapa do software
UML ContaBancaria

Legenda:

  • 🔒 - (Private): Visível só na classe.
  • 🛡️ # (Protected): Visível filhos.
  • 🌍 + (Public): Visível a todos.

Estereótipos e Modificadores:

  • << interface >>: Contrato puro.
  • Itálico: Classe ou método Abstrato.
  • Sublinhado: Membro Estático (compartilhado).
🔗 UML -> Código (Rosetta Stone)
Traduzindo diagramas para C#

➡️ Associação

"Usa um"

UML Associacao

💠 Agregação

"Tem um" (Fraco)

UML Agregacao

♦️ Composição

"É parte de" (Forte)

UML Composicao
🧱 Princípios SOLID (Intro)
A base do Design Limpo

S - Single Responsibility (SRP)

"Uma classe deve ter apenas um motivo para mudar."

Exemplo Ruim: Classe Relatorio que calcula dados E salva em PDF.

Refatoração: CalculadoraRelatorio e GeradorPDF.

O - Open/Closed (OCP)

"Aberto para extensão, fechado para modificação."

Exemplo Ruim: if (tipo == "Boleto") ... else if ...

Refatoração: Polimorfismo! IPagamento.Processar().

Alta Coesão (Coisas relacionadas juntas) e Baixo Acoplamento (Coisas independentes separadas).

🧪 Filosofia TDD
Design First, Code Later
"O ato de escrever um teste força você a considerar a interface e a usabilidade do código antes da implementação."

Benefícios Reais:

  • Design Emergente: O código cresce organicamente.
  • Coragem: Refatore sem medo de quebrar.
  • Documentação Viva: O teste diz O QUE o código faz.

Custo de Mudança

Com TDD = Constante (Graph line flat)

Sem TDD = Exponencial (Code rot)

🚦 O Ciclo Red-Green-Refactor
Passo a Passo
RED

1. Teste Falha

Escreva o teste para uma funcionalidade que NÃO existe. O erro deve ser de compilação ou asserção.

➡️
GREEN

2. Faça Passar

Cometa pecados! Use constantes, `return true`, copie e cole. O objetivo é ver a barra verde rápido.

➡️
REFACTOR

3. Limpe a Casa

Agora sim: Remova duplicação, renomeie variáveis, aplique Patterns. O teste garante que nada quebrou.

💻 Exemplo: Calculadora de Frete
// 1. RED (Teste) [Test] public void Frete_DeveSerZero_ParaPedidosAcimaDe200() { var calc = new FreteCalculadora(); var valor = calc.Calcular(pedidoTotal: 250); Assert.AreEqual(0, valor); } // 2. GREEN (Implementação "safada") public class FreteCalculadora { public decimal Calcular(decimal pedidoTotal) { return 0; // Hardcoded! Passou o teste. } } // 3. REFACTOR (Depois de adicionar mais testes...) public decimal Calcular(decimal pedidoTotal) { if (pedidoTotal > 200) return 0; return 20; // Lógica real }
⚠️ TDD Anti-Patterns
O que NÃO fazer

🤥 O Mentiroso (The Liar)

O teste passa, mas não testa nada.

Assert.IsTrue(true); // Falso positivo

🏗️ Setup Excessivo

Se você precisa de 50 linhas para instanciar a classe, seu design está altamente acoplado.

🕵️ O Inspetor Privado

Tentar testar métodos `private` ou `internal`. Teste apenas a interface pública!

🐢 A Tartaruga

Testes lentos (que tocam Banco de Dados ou Rede). Teste unitário deve rodar em milissegundos.

🤖 TDD na Era dos Agentes de IA
Foco em outputs, comportamento e falhas

Hoje, quase ninguém escreve tudo na mão. O trabalho mudou: ler, entender e gerenciar os outputs gerados por agentes.

Antes (manual)

  • Escrever linha por linha.
  • Debug de implementação local.
  • Decisão baseada em estilo pessoal.

Agora (com IA)

  • Definir cenários de teste primeiro.
  • Delegar geração para agentes especializados.
  • Controlar qualidade por evidência (testes + métricas).
🧰 Playbook: Skills + Agentes com TDD
Estrutura mínima de trabalho

Boas práticas operacionais

  • Skill de contexto: arquitetura, convenções e domínio.
  • Agente red: escreve/expande testes e cenários de falha.
  • Agente green: implementa o mínimo para passar.
  • Agente refactor: simplifica sem quebrar contratos.
  • Agente reviewer: caça regressão e risco técnico.
// Pipeline orientado a output RED: teste falha e reproduz bug real GREEN: patch minimo, sem escopo extra REFACTOR: melhora design sem alterar comportamento CHECK: cobertura, risco, performance, seguranca // Regra de ouro Sem evidencia (teste/metricas), sem merge.

O prompt não é especificação final; os testes são.

🧪 Analogia: Engenharia de Chips
Ninguém rastreia transistor por transistor

Em chips modernos, há bilhões de transistores. É humanamente impossível gerenciar um por um. O engenheiro valida comportamento emergente por simulação, teste e especificação.

No design de chips

  • Verificação funcional por cenários.
  • Validação temporal e consumo de energia.
  • Teste de corner cases e tolerância a falhas.

No software com IA

  • Validar outputs em cenários críticos.
  • Mensurar latência, custo e confiabilidade.
  • Tratar falhas como parte do design.
📐 ATAM para Agentes de IA
Gerenciar comportamento e falhas, não só código

Quality Attributes (alvos)

  • Confiabilidade: taxa de testes verdes por release.
  • Segurança: ausência de segredos e vulnerabilidades.
  • Modificabilidade: tempo para ajustar requisito.
  • Observabilidade: logs e rastreio de decisão do agente.

Cenários ATAM (exemplos)

  • Agente gera patch que passa unitário e quebra integração.
  • Modelo muda versão e altera estilo de código inesperadamente.
  • Falha de teste intermitente mascara regressão real.
  • Refactor melhora legibilidade mas piora latência.
Resumo: o engenheiro moderno governa sistema por evidência de comportamento. Ler código continua essencial, mas o diferencial está em gerenciar output e risco.
📚 A Bíblia Visual
Onde aprender mais?

Refactoring.Guru

"O melhor recurso visual para aprender Padrões de Projeto e Refatoração."

🔗 Acessar Refactoring Guru 🚀 Repositório da Aula (aula_dotnet_cars)

Essencial para entender Singleton, Factory, Strategy, Observer, etc.