
Introdução à Engenharia de Software: Princípios, Práticas e Metodologias em 2026
Escrever código é como aprender a martelar um prego; engenharia de software é aprender a construir um arranha-céu que não caia no primeiro vendaval. Se você acha que programar é apenas "fazer funcionar", prepare-se: o verdadeiro desafio começa quando o sistema cresce, os usuários aumentam e o que era uma solução simples vira um monstro impossível de manter.
Neste guia, vamos explorar como transformar o ato de codificar em uma disciplina profissional. Vamos entender por que as metodologias ágeis não são apenas reuniões chatas, como os princípios SOLID salvam seu código do caos e como a arquitetura de software permite que sua aplicação evolua por anos sem precisar ser reescrita do zero.

O Que é Engenharia de Software?
A engenharia de software é uma disciplina que aplica princípios de engenharia para o desenvolvimento de software de forma profissional e confiável. Assim como a engenharia civil tem princípios e práticas para construir pontes e edifícios seguros, a engenharia de software tem princípios para construir sistemas de software confiáveis.
Os objetivos da engenharia de software incluem:
- Qualidade: Produzir software que atenda às necessidades do usuário
- Eficiência: Desenvolver software dentro do orçamento e prazo
- Manutenibilidade: Criar software fácil de modificar e estender
- Confiabilidade: Garantir que o software funcione corretamente em condições esperadas
- Segurança: Proteger o software contra ameaças e vulnerabilidades
A engenharia de software envolve:
- Análise de requisitos
- Projeto de software
- Implementação
- Testes
- Manutenção
- Gerenciamento de configuração
- Qualidade de software
- Gerenciamento de projetos
Ciclo de Vida de Desenvolvimento de Software
O ciclo de vida define as fases pelas quais um software passa desde sua concepção até sua desativação. Existem vários modelos de ciclo de vida:
Modelo Cascata (Waterfall)
O modelo cascata é um modelo sequencial onde cada fase deve ser concluída antes da próxima começar:
- Análise de Requisitos: Coleta e análise dos requisitos do sistema
- Projeto do Sistema: Projeto arquitetural e de componentes
- Implementação: Escrita do código fonte
- Testes: Verificação e validação do software
- Implantação: Instalação e entrega do software ao usuário
- Manutenção: Correções e melhorias após a implantação
graph TD
A[Análise de Requisitos] --> B[Projeto do Sistema]
B --> C[Implementação]
C --> D[Testes]
D --> E[Implantação]
E --> F[Manutenção]Vantagens:
- Fácil de entender e gerenciar
- Documentação completa
- Adequado para projetos com requisitos bem definidos
Desvantagens:
- Difícil acomodar mudanças
- Feedback do usuário só no final
- Risco elevado em projetos complexos
Modelo em V
O modelo em V é uma extensão do modelo cascata que enfatiza os testes em cada fase:
graph TD
A[Requisitos] --> B[Projeto do Sistema]
B --> C[Projeto Detalhado]
C --> D[Implementação]
D --> E[Teste de Unidade]
E --> F[Teste de Integração]
F --> G[Teste de Sistema]
G --> H[Teste de Aceitação]Metodologias Ágeis
As metodologias ágeis surgiram para responder às limitações dos modelos tradicionais, priorizando a colaboração, a adaptação e a entrega contínua de valor.
Manifesto Ágil
O Manifesto Ágil, criado em 2001, define os valores fundamentais:
- Indivíduos e interações mais que processos e ferramentas
- Software funcionando mais que documentação abrangente
- Colaboração com o cliente mais que negociação de contratos
- Resposta à mudança mais que seguir um plano
Scrum
Scrum é um framework ágil composto por papéis, eventos e artefatos:
Papéis:
- Product Owner: Responsável pelo backlog e valor do produto
- Scrum Master: Facilitador do processo Scrum
- Time de Desenvolvimento: Equipe multifuncional que entrega o produto
Eventos:
- Sprint: Iteração de 2-4 semanas
- Planejamento de Sprint: Planejamento do trabalho
- Daily Scrum: Reunião diária de 15 minutos
- Revisão de Sprint: Demonstração do incremento
- Retrospectiva de Sprint: Melhoria contínua
Artefatos:
- Product Backlog: Lista de funcionalidades desejadas
- Sprint Backlog: Trabalhos selecionados para o sprint
- Incremento: Resultado potencialmente liberável do sprint
# Exemplo de backlog de produto em Scrum
class ProductBacklogItem:
def __init__(self, id, descricao, prioridade, estimativa, valor_negocio):
self.id = id
self.descricao = descricao
self.prioridade = prioridade
self.estimativa = estimativa # Pontos de história
self.valor_negocio = valor_negocio
self.estado = "não iniciado"
class Sprint:
def __init__(self, numero, duracao_semanas, objetivo):
self.numero = numero
self.duracao_semanas = duracao_semanas
self.objetivo = objetivo
self.itens = []
self.data_inicio = None
self.data_fim = None
def adicionar_item(self, item):
"""Adiciona item ao sprint backlog"""
self.itens.append(item)
item.estado = "em andamento"
# Exemplo de implementação de sprint
sprint_atual = Sprint(1, 2, "Implementar sistema de login")
sprint_atual.adicionar_item(ProductBacklogItem(
1, "Login de usuário com validação", 1, 5, "Alto"
))Princípios de Engenharia de Software
Princípios SOLID
SOLID é um acrônimo para cinco princípios de design de classes:
- S - Princípio da Responsabilidade Única (SRP)
- Uma classe deve ter apenas um motivo para mudar
- Cada classe deve ter uma única responsabilidade
# Exemplo de violação do SRP
class UsuarioSRPViolacao:
def __init__(self, nome, email):
self.nome = nome
self.email = email
def salvar_no_banco(self):
# Lógica de persistência
pass
def enviar_email(self):
# Lógica de envio de email
pass
def gerar_relatorio(self):
# Lógica de geração de relatório
pass
# Exemplo de aplicação correta do SRP
class Usuario:
def __init__(self, nome, email):
self.nome = nome
self.email = email
class UsuarioRepositorio:
def salvar(self, usuario):
# Lógica de persistência
pass
def buscar_por_email(self, email):
# Lógica de busca
pass
class ServicoEmail:
def enviar(self, destinatario, assunto, corpo):
# Lógica de envio de email
pass
class GeradorRelatorio:
def gerar(self, usuarios):
# Lógica de geração de relatório
pass- O - Princípio Aberto/Fechado (OCP)
- Entidades devem estar abertas para extensão, mas fechadas para modificação
from abc import ABC, abstractmethod
class Forma(ABC):
@abstractmethod
def calcular_area(self):
pass
class Retangulo(Forma):
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
def calcular_area(self):
return self.largura * self.altura
class Circulo(Forma):
def __init__(self, raio):
self.raio = raio
def calcular_area(self):
return 3.14159 * self.raio ** 2
# Novas formas podem ser adicionadas sem modificar o código existente
class Triangulo(Forma):
def __init__(self, base, altura):
self.base = base
self.altura = altura
def calcular_area(self):
return (self.base * self.altura) / 2
# Função que trabalha com qualquer forma (extensibilidade)
def calcular_area_total(formas):
return sum(forma.calcular_area() for forma in formas)-
L - Princípio da Substituição de Liskov (LSP)
- Objetos de uma classe derivada devem poder substituir objetos da classe base
-
I - Princípio da Segregação de Interface (ISP)
- Clientes não devem ser forçados a depender de interfaces que não usam
-
D - Princípio da Inversão de Dependência (DIP)
- Módulos de alto nível não devem depender de módulos de baixo nível, ambos devem depender de abstrações
# Exemplo de DIP
class NotificacaoService:
def __init__(self, provedor_notificacao):
self.provedor = provedor_notificacao # Injeção de dependência
def enviar_notificacao(self, mensagem, destinatario):
self.provedor.enviar(mensagem, destinatario)
class ProvedorEmail:
def enviar(self, mensagem, destinatario):
print(f"Enviando email para {destinatario}: {mensagem}")
class ProvedorSMS:
def enviar(self, mensagem, destinatario):
print(f"Enviando SMS para {destinatario}: {mensagem}")
# O serviço não depende de implementações específicas
notificacao_email = NotificacaoService(ProvedorEmail())
notificacao_sms = NotificacaoService(ProvedorSMS())Princípios DRY, KISS e YAGNI
- DRY (Don't Repeat Yourself): Não repita código; reutilize e abstraia
- KISS (Keep It Simple, Stupid): Mantenha o código simples e direto
- YAGNI (You Aren't Gonna Need It): Não implemente funcionalidades que não serão usadas
Arquitetura de Software
A arquitetura de software define a estrutura de um sistema, incluindo componentes, suas relações e os princípios que orientam seu design e evolução.
Padrões Arquiteturais Comuns
Arquitetura em Camadas (Layered Architecture)
# Exemplo de arquitetura em camadas
class EntidadeUsuario:
def __init__(self, id, nome, email):
self.id = id
self.nome = nome
self.email = email
# Camada de dados
class UsuarioRepositorio:
def __init__(self, conexao_banco):
self.conexao = conexao_banco
def salvar(self, usuario):
# Lógica de persistência
pass
def buscar_por_id(self, id):
# Lógica de busca
pass
# Camada de negócio
class ServicoUsuario:
def __init__(self, repositorio):
self.repositorio = repositorio
def criar_usuario(self, nome, email):
# Validações de negócio
if not email or '@' not in email:
raise ValueError("Email inválido")
usuario = EntidadeUsuario(None, nome, email)
return self.repositorio.salvar(usuario)
def atualizar_perfil(self, id, dados_atualizados):
# Lógica de negócio
pass
# Camada de apresentação
class ControladorUsuario:
def __init__(self, servico_usuario):
self.servico = servico_usuario
def criar_usuario(self, dados):
try:
usuario = self.servico.criar_usuario(
dados['nome'],
dados['email']
)
return {"sucesso": True, "usuario": usuario}
except ValueError as e:
return {"sucesso": False, "erro": str(e)}Arquitetura Hexagonal (Ports and Adapters)
A arquitetura hexagonal isola a lógica de negócio de detalhes técnicos:
from abc import ABC, abstractmethod
# Portas (interfaces)
class PortaUsuario(ABC):
@abstractmethod
def buscar_usuario(self, id):
pass
@abstractmethod
def salvar_usuario(self, usuario):
pass
class PortaEmail(ABC):
@abstractmethod
def enviar_email(self, destinatario, assunto, corpo):
pass
# Lógica de negócio (núcleo)
class ServicoCadastroUsuario:
def __init__(self, porta_usuario, porta_email):
self.porta_usuario = porta_usuario
self.porta_email = porta_email
def cadastrar(self, nome, email):
# Lógica de negócio
usuario = self.porta_usuario.salvar_usuario({
'nome': nome,
'email': email
})
self.porta_email.enviar_email(
email,
"Bem-vindo!",
f"Olá {nome}, seja bem-vindo!"
)
return usuario
# Adaptadores (implementações concretas)
class AdaptadorUsuarioBanco(PortaUsuario):
def buscar_usuario(self, id):
# Implementação com banco de dados
pass
def salvar_usuario(self, usuario):
# Implementação com banco de dados
pass
class AdaptadorEmailSMTP(PortaEmail):
def enviar_email(self, destinatario, assunto, corpo):
# Implementação com SMTP
pass
# Configuração
servico = ServicoCadastroUsuario(
AdaptadorUsuarioBanco(),
AdaptadorEmailSMTP()
)Arquitetura de Microsserviços
Microsserviços dividem uma aplicação em serviços pequenos e independentes:
# Exemplo conceitual de microsserviços
import requests
class ServicoUsuario:
"""Serviço independente de gerenciamento de usuários"""
def __init__(self, url_base):
self.url_base = url_base
def obter_usuario(self, id):
resposta = requests.get(f"{self.url_base}/usuarios/{id}")
return resposta.json()
def criar_usuario(self, dados):
resposta = requests.post(f"{self.url_base}/usuarios", json=dados)
return resposta.json()
class ServicoNotificacao:
"""Serviço independente de notificações"""
def __init__(self, url_base):
self.url_base = url_base
def enviar_notificacao(self, usuario_id, mensagem):
dados = {
'usuario_id': usuario_id,
'mensagem': mensagem
}
resposta = requests.post(f"{self.url_base}/notificacoes", json=dados)
return resposta.json()
class Orquestrador:
"""Coordenador que chama serviços independentes"""
def __init__(self):
self.servico_usuario = ServicoUsuario("http://servico-usuarios:3000")
self.servico_notificacao = ServicoNotificacao("http://servico-notificacoes:3001")
def cadastrar_usuario_com_notificacao(self, dados_usuario):
# Cria usuário
usuario = self.servico_usuario.criar_usuario(dados_usuario)
# Envia notificação
self.servico_notificacao.enviar_notificacao(
usuario['id'],
"Bem-vindo ao sistema!"
)
return usuarioPráticas de Desenvolvimento
Programação em Pares (Pair Programming)
Dois desenvolvedores trabalham juntos no mesmo código, um pilotando (escrevendo código) e outro navegando (revisando e planejando).
Integração Contínua e Entrega Contínua (CI/CD)
Processos automatizados para integrar, testar e entregar código:
# Exemplo de pipeline CI/CD com GitHub Actions
name: CI/CD Pipeline
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: pytest --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
- name: Security scan
run: bandit -r src/
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build Docker image
run: |
docker build -t minha-app:${{ github.sha }} .
docker tag minha-app:${{ github.sha }} minha-app:latest
- name: Push to registry
run: |
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker push minha-app:${{ github.sha }}
deploy:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- name: Deploy to production
run: |
# Comandos de deploy (ex: kubectl, aws, etc.)
echo "Deploy realizado com sucesso!"Revisão de Código (Code Review)
Processo de verificação do código por outros desenvolvedores antes de ser mesclado:
# Exemplo de boas práticas em revisão de código
def calcular_desconto(valor, tipo_cliente):
"""
Calcula desconto com base no tipo de cliente.
Args:
valor (float): Valor original
tipo_cliente (str): Tipo do cliente
Returns:
float: Valor com desconto aplicado
"""
if valor <= 0:
raise ValueError("Valor deve ser positivo")
if tipo_cliente == "premium":
desconto = 0.15
elif tipo_cliente == "gold":
desconto = 0.10
elif tipo_cliente == "silver":
desconto = 0.05
else:
desconto = 0.0
return valor * (1 - desconto)
# Exemplo de revisão:
# ✅ Documentação clara
# ✅ Validação de entrada
# ✅ Lógica bem estruturada
# ✅ Nomes de variáveis descritivosQualidade de Software
Métricas de Código
- Cobertura de Testes: Porcentagem do código exercida pelos testes
- Complexidade Ciclomática: Medida da complexidade de um módulo
- Duplicação de Código: Proporção de código duplicado
- Tamanho do Módulo: Linhas de código por módulo
Ferramentas de Análise Estática
# Exemplo de ferramentas de análise de código
# Python
pip install flake8 black mypy bandit
# Análise de estilo
flake8 src/
# Formatação automática
black src/
# Tipagem estática
mypy src/
# Análise de segurança
bandit -r src/
# JavaScript
npm install eslint prettier jest
# Análise de código
npx eslint src/
# Formatação
npx prettier --write src/Segurança em Engenharia de Software
Princípios de Segurança
- Defesa em Profundidade: Múltiplas camadas de proteção
- Princípio do Menor Privilégio: Acesso mínimo necessário
- Segurança por Design: Segurança desde o início do projeto
Práticas Comuns
- OWASP Top 10: Lista das principais vulnerabilidades web
- Validação de Entrada: Sempre validar dados de entrada
- Codificação Segura: Prevenir injeção de código e outros ataques
- Gerenciamento de Segredos: Não armazenar credenciais no código
# Exemplo de práticas de segurança
import sqlite3
from werkzeug.security import check_password_hash, generate_password_hash
def validar_entrada(usuario_input):
"""Valida entrada do usuário para prevenir injeção de código"""
if not isinstance(usuario_input, str):
raise ValueError("Entrada deve ser string")
# Remover caracteres perigosos
entrada_sanitizada = usuario_input.strip()
# Validar formato (ex: email)
if '@' not in entrada_sanitizada:
raise ValueError("Formato inválido")
return entrada_sanitizada
def autenticar_usuario(usuario, senha):
"""Autenticação segura com hashing de senha"""
conexao = sqlite3.connect('banco.db')
cursor = conexao.cursor()
# Usar consultas parametrizadas para prevenir SQL injection
cursor.execute(
"SELECT senha_hash FROM usuarios WHERE nome = ?",
(usuario,)
)
resultado = cursor.fetchone()
if resultado and check_password_hash(resultado[0], senha):
return True
return False
def criar_usuario_seguro(nome, email, senha):
"""Criação de usuário com segurança"""
conexao = sqlite3.connect('banco.db')
cursor = conexao.cursor()
# Validar entrada
nome = validar_entrada(nome)
email = validar_entrada(email)
# Hash da senha
senha_hash = generate_password_hash(senha)
# Inserção segura
cursor.execute(
"INSERT INTO usuarios (nome, email, senha_hash) VALUES (?, ?, ?)",
(nome, email, senha_hash)
)
conexao.commit()
conexao.close()Casos de Uso Reais
Sistemas de Pagamento
- Arquitetura em microsserviços para alta disponibilidade
- Padrões de design para isolamento de falhas
- Testes automatizados para garantir integridade financeira
Redes Sociais
- Escalabilidade horizontal para milhões de usuários
- Arquitetura de eventos para notificações em tempo real
- Segurança para proteger dados pessoais
Sistemas Bancários
- Conformidade com regulamentações
- Transações ACID para integridade de dados
- Auditoria completa de todas as operações
Limitações e Desafios
A engenharia de software enfrenta diversos desafios:
- Complexidade crescente: Sistemas cada vez mais complexos
- Mudanças rápidas: Necessidade de adaptação constante
- Equipes distribuídas: Coordenação entre equipes remotas
- Segurança: Ameaças cibernéticas cada vez mais sofisticadas
- Qualidade vs. Velocidade: Equilíbrio entre entrega rápida e qualidade
Passo a Passo: Implementando uma Prática de Engenharia de Software
Vamos criar um exemplo completo de como implementar uma prática de engenharia de software em um projeto real:
# Projeto: Sistema de Gerenciamento de Tarefas
# 1. Definir requisitos e arquitetura
"""
Requisitos:
- Criar, ler, atualizar e deletar tarefas
- Atribuir tarefas a usuários
- Marcar tarefas como concluídas
- Filtrar tarefas por status e usuário
Arquitetura:
- Camada de apresentação (API REST)
- Camada de negócio (lógica de tarefas)
- Camada de dados (repositório de tarefas)
"""
# 2. Implementar com princípios de engenharia
from datetime import datetime
from enum import Enum
from typing import List, Optional
import sqlite3
class StatusTarefa(Enum):
PENDENTE = "pendente"
EM_ANDAMENTO = "em_andamento"
CONCLUIDA = "concluida"
class Tarefa:
def __init__(self, id: Optional[int], titulo: str, descricao: str,
usuario_id: int, data_criacao: datetime,
status: StatusTarefa = StatusTarefa.PENDENTE):
self.id = id
self.titulo = titulo
self.descricao = descricao
self.usuario_id = usuario_id
self.data_criacao = data_criacao
self.status = status
class RepositorioTarefas:
"""Camada de dados - abstrai o acesso ao banco de dados"""
def __init__(self, conexao_db: sqlite3.Connection):
self.conexao = conexao_db
self._criar_tabela()
def _criar_tabela(self):
"""Cria a tabela de tarefas se não existir"""
self.conexao.execute('''
CREATE TABLE IF NOT EXISTS tarefas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titulo TEXT NOT NULL,
descricao TEXT,
usuario_id INTEGER NOT NULL,
data_criacao TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'pendente'
)
''')
self.conexao.commit()
def criar(self, tarefa: Tarefa) -> Tarefa:
"""Cria uma nova tarefa"""
cursor = self.conexao.execute('''
INSERT INTO tarefas (titulo, descricao, usuario_id, status)
VALUES (?, ?, ?, ?)
''', (tarefa.titulo, tarefa.descricao, tarefa.usuario_id, tarefa.status.value))
tarefa.id = cursor.lastrowid
self.conexao.commit()
return tarefa
def buscar_por_id(self, id: int) -> Optional[Tarefa]:
"""Busca tarefa por ID"""
cursor = self.conexao.execute('''
SELECT id, titulo, descricao, usuario_id, data_criacao, status
FROM tarefas WHERE id = ?
''', (id,))
resultado = cursor.fetchone()
if resultado:
return Tarefa(
id=resultado[0],
titulo=resultado[1],
descricao=resultado[2],
usuario_id=resultado[3],
data_criacao=resultado[4],
status=StatusTarefa(resultado[5])
)
return None
def buscar_por_usuario(self, usuario_id: int) -> List[Tarefa]:
"""Busca tarefas por usuário"""
cursor = self.conexao.execute('''
SELECT id, titulo, descricao, usuario_id, data_criacao, status
FROM tarefas WHERE usuario_id = ?
ORDER BY data_criacao DESC
''', (usuario_id,))
tarefas = []
for linha in cursor.fetchall():
tarefa = Tarefa(
id=linha[0],
titulo=linha[1],
descricao=linha[2],
usuario_id=linha[3],
data_criacao=linha[4],
status=StatusTarefa(linha[5])
)
tarefas.append(tarefa)
return tarefas
class ServicoTarefas:
"""Camada de negócio - lógica de domínio"""
def __init__(self, repositorio: RepositorioTarefas):
self.repositorio = repositorio
def criar_tarefa(self, titulo: str, descricao: str, usuario_id: int) -> Tarefa:
"""Cria uma nova tarefa com validações de negócio"""
if not titulo.strip():
raise ValueError("Título é obrigatório")
if len(titulo) > 200:
raise ValueError("Título muito longo (máximo 200 caracteres)")
if usuario_id <= 0:
raise ValueError("ID de usuário inválido")
tarefa = Tarefa(
id=None,
titulo=titulo,
descricao=descricao,
usuario_id=usuario_id,
data_criacao=datetime.now()
)
return self.repositorio.criar(tarefa)
def atualizar_status(self, tarefa_id: int, novo_status: StatusTarefa) -> bool:
"""Atualiza o status de uma tarefa"""
tarefa = self.repositorio.buscar_por_id(tarefa_id)
if not tarefa:
raise ValueError("Tarefa não encontrada")
# Lógica de negócio: não pode concluir uma tarefa já concluída
if tarefa.status == StatusTarefa.CONCLUIDA and novo_status == StatusTarefa.CONCLUIDA:
return False # Não há mudança
# Atualizar status (isso seria feito diretamente no repositório em um sistema real)
# Para este exemplo, vamos apenas retornar sucesso
return True
# 3. Testes automatizados
import unittest
from unittest.mock import Mock
class TesteServicoTarefas(unittest.TestCase):
def setUp(self):
"""Configuração antes de cada teste"""
self.repositorio_mock = Mock(spec=RepositorioTarefas)
self.servico = ServicoTarefas(self.repositorio_mock)
def test_criar_tarefa_com_dados_validos(self):
"""Testa criação de tarefa com dados válidos"""
tarefa_mock = Tarefa(
id=1,
titulo="Teste",
descricao="Descrição de teste",
usuario_id=1,
data_criacao=datetime.now()
)
self.repositorio_mock.criar.return_value = tarefa_mock
tarefa = self.servico.criar_tarefa("Teste", "Descrição de teste", 1)
self.assertEqual(tarefa.titulo, "Teste")
self.repositorio_mock.criar.assert_called_once()
def test_criar_tarefa_titulo_vazio(self):
"""Testa criação de tarefa com título vazio (deve lançar exceção)"""
with self.assertRaises(ValueError):
self.servico.criar_tarefa("", "Descrição", 1)
def test_atualizar_status_tarefa_nao_encontrada(self):
"""Testa atualização de status de tarefa inexistente"""
self.repositorio_mock.buscar_por_id.return_value = None
with self.assertRaises(ValueError):
self.servico.atualizar_status(999, StatusTarefa.CONCLUIDA)
# 4. Implementação da API (exemplo com Flask)
from flask import Flask, request, jsonify
app = Flask(__name__)
# Configuração do banco de dados
conexao_db = sqlite3.connect(':memory:') # Para exemplo, usar arquivo real em produção
repositorio = RepositorioTarefas(conexao_db)
servico = ServicoTarefas(repositorio)
@app.route('/tarefas', methods=['POST'])
def criar_tarefa():
"""Endpoint para criar nova tarefa"""
dados = request.get_json()
try:
tarefa = servico.criar_tarefa(
dados['titulo'],
dados.get('descricao', ''),
dados['usuario_id']
)
return jsonify({
'id': tarefa.id,
'titulo': tarefa.titulo,
'descricao': tarefa.descricao,
'usuario_id': tarefa.usuario_id,
'status': tarefa.status.value,
'data_criacao': tarefa.data_criacao.isoformat()
}), 201
except ValueError as e:
return jsonify({'erro': str(e)}), 400
@app.route('/tarefas/<int:tarefa_id>', methods=['GET'])
def obter_tarefa(tarefa_id):
"""Endpoint para obter tarefa por ID"""
tarefa = repositorio.buscar_por_id(tarefa_id)
if tarefa:
return jsonify({
'id': tarefa.id,
'titulo': tarefa.titulo,
'descricao': tarefa.descricao,
'usuario_id': tarefa.usuario_id,
'status': tarefa.status.value,
'data_criacao': tarefa.data_criacao
})
return jsonify({'erro': 'Tarefa não encontrada'}), 404
if __name__ == '__main__':
app.run(debug=True)Comparação de Metodologias
| Metodologia | Foco | Adaptação a Mudanças | Documentação | Colaboração | |-------------|------|---------------------|--------------|-------------| | Cascata | Planejamento detalhado | Baixa | Alta | Formal | | Ágil | Entrega contínua | Alta | Leve | Colaborativa | | Lean | Eliminação de desperdícios | Média | Mínima | Equipe | | DevOps | Entrega contínua | Alta | Automatizada | Transversal |
Conclusão
A engenharia de software é uma disciplina fundamental para desenvolver sistemas de alta qualidade, escaláveis e confiáveis. Com a crescente complexidade dos sistemas modernos, aplicar princípios e práticas de engenharia se torna cada vez mais essencial.
No momento, as metodologias ágeis, práticas de DevOps e arquiteturas modernas como microsserviços são as tendências dominantes, combinadas com uma forte ênfase em segurança, qualidade de código e automação de testes.
A tendência é que a engenharia de software continue evoluindo com novas práticas, ferramentas e metodologias que permitam desenvolver software de forma mais eficiente, segura e sustentável.
Você já aplicou práticas de engenharia de software em seus projetos? Compartilhe sua experiência nos comentários e como isso melhorou a qualidade do seu código.
Glossário Técnico
- Arquitetura de Software: Estrutura fundamental de um sistema de software.
- Metodologia Ágil: Abordagem iterativa e incremental de desenvolvimento.
- CI/CD: Integração e entrega contínuas com automação.
- SOLID: Conjunto de princípios de design de classes.
- Microsserviços: Arquitetura baseada em serviços pequenos e independentes.
- DDD: Domain-Driven Design - design orientado ao domínio.
- TDD: Test-Driven Development - desenvolvimento orientado a testes.
Referências
- IEEE. Guide to the Software Engineering Body of Knowledge (SWEBOK). Guia abrangente do conhecimento em engenharia de software.
- Agile Alliance. Agile Principles and Practices. Princípios e práticas ágeis.
- Martin Fowler. Software Architecture Guide. Artigos sobre arquitetura de software.
