flowchart TB
A[Apresentação\ninterface com o usuário] --> B[Lógica de Negócio\nregras e serviços]
B --> C[Acesso a Dados\npersistência]
C --> D[(Laboratório de Engenharia de Software)]
style A fill:#cfe2ff,stroke:#084298
style B fill:#d1e7dd,stroke:#0f5132
style C fill:#fff3cd,stroke:#664d03
style D fill:#e2d9f3,stroke:#59359a
Módulo 10 — Projeto de Software: aspectos técnicos e gerenciais
Onde estamos. Nos módulos anteriores, aprendemos a descobrir e registrar o que um sistema deve fazer. Agora damos um passo decisivo: sair do “o quê” e entrar no “como”. Projetar software é decidir a forma interna daquilo que os requisitos apenas prometem, e ao mesmo tempo organizar o trabalho de quem vai construí-lo. Quero que você chegue à aula entendendo que projeto tem duas faces inseparáveis — a técnica e a gerencial — e que negligenciar qualquer uma delas condena o sistema.
Deixe-me abrir com uma confusão que atrapalha muitos estudantes. Existe uma tentação de achar que, depois de levantar os requisitos, basta “começar a programar”. Essa ideia ignora um trabalho intelectual enorme que fica entre entender o problema e digitar código: o projeto. E há uma segunda tentação, ainda mais silenciosa, de achar que projeto é só uma questão técnica — de classes, camadas e algoritmos. Não é. Um projeto de software também é um empreendimento que consome tempo, dinheiro e pessoas, e pode fracassar não por uma decisão técnica ruim, mas por uma estimativa irreal ou um risco ignorado. Por isso, este módulo caminha nas duas pernas: a engenharia do artefato e a gerência do esforço.
Análise e projeto não são a mesma coisa
Começo pela distinção que organiza tudo o que virá. Análise e projeto (em inglês, design) são atividades vizinhas, mas com propósitos opostos. A análise se preocupa com o problema: ela descreve o que o sistema deve fazer, quais conceitos do domínio existem, quais regras de negócio se aplicam, tudo isso de forma idealmente independente de como será implementado. O projeto se preocupa com a solução: ele decide como o sistema será estruturado internamente para satisfazer aquela análise, agora levando em conta as restrições reais — a tecnologia, o desempenho, a plataforma, a equipe.
Bezerra descreve essa passagem com uma imagem que gosto de repetir: a análise responde “o que”, o projeto responde “como”. Na análise, você pode dizer que “um pedido possui itens e calcula seu total”. No projeto, você decide se esse cálculo mora em uma classe de serviço, se os itens vêm de um banco relacional, se há uma camada intermediária que os busca. A mesma análise pode gerar projetos muito diferentes, e é aí que reside a competência de engenharia: escolher, entre as soluções possíveis, aquela que melhor equilibra os atributos de qualidade que já estudamos.
Análise fala do problema no vocabulário do domínio; projeto fala da solução no vocabulário da tecnologia. Confundir as duas leva a modelar prematuramente detalhes de implementação — e a perder a clareza do problema. Sommerville insiste que boas decisões de projeto pressupõem uma análise sólida por trás.
O projeto arquitetural e seus estilos
Dentro do projeto, a primeira decisão é a de maior alcance e a mais cara de reverter: a arquitetura. Projetar a arquitetura é decidir como o sistema se divide em grandes partes, qual a responsabilidade de cada uma e como elas se comunicam. Pressman e Sommerville tratam a arquitetura como o esqueleto sobre o qual tudo o mais é pendurado; errar aqui contamina cada módulo construído depois. Ao longo das décadas, a comunidade destilou alguns estilos arquiteturais recorrentes — soluções de organização que provaram valor e que você reconhece em quase todo sistema real.
O estilo em camadas organiza o sistema em faixas horizontais, cada uma oferecendo serviços à camada imediatamente acima e apoiando-se na de baixo. Uma organização comum separa apresentação, lógica de negócio e acesso a dados. A regra que dá força a esse estilo é a dependência unidirecional: uma camada só conhece a de baixo, nunca a de cima. Isso permite trocar a interface gráfica sem mexer nas regras de negócio, ou trocar o banco sem reescrever a lógica. Veja o desenho:
O estilo cliente-servidor distribui responsabilidades entre um provedor de serviços, o servidor, e consumidores desses serviços, os clientes, que se comunicam por uma rede. É a arquitetura por trás da web e de quase todo aplicativo conectado. Sua vantagem é concentrar recursos e regras no servidor, permitindo muitos clientes leves; seu desafio é a dependência da rede e a necessidade de tratar latência e falhas de comunicação.
O estilo modelo-visão-controlador (MVC) separa três preocupações que tendem a se enredar em interfaces: o modelo, que guarda os dados e as regras; a visão, que apresenta ao usuário; e o controlador, que interpreta as ações do usuário e coordena os dois. Assim, a mesma informação pode ser exibida de várias formas sem duplicar a lógica, e a lógica pode evoluir sem quebrar a tela. Guarde que esses estilos não são mutuamente exclusivos: é comum um sistema em camadas usar MVC na apresentação e cliente-servidor entre suas fronteiras físicas.
Decompor em módulos: a arte de dividir bem
Escolhido o esqueleto, o projeto desce ao nível dos módulos. Decompor um sistema é quebrá-lo em unidades menores, cada uma com uma responsabilidade definida, que juntas realizam o todo. A pergunta que importa não é se dividir — sistemas grandes são incompreensíveis sem divisão —, mas como dividir. Uma decomposição ruim espalha uma mesma responsabilidade por vários módulos e amontoa responsabilidades distintas dentro de um só. Uma decomposição boa faz cada módulo contar uma história única e conversar pouco com os outros. Para julgar isso com objetividade, a engenharia de software oferece dois conceitos que você levará para a carreira inteira: coesão e acoplamento.
Coesão e acoplamento: os dois eixos da boa decomposição
Coesão mede o quanto os elementos de um módulo pertencem uns aos outros. Um módulo altamente coeso faz uma coisa e a faz bem: tudo dentro dele existe por um mesmo propósito. Um módulo de baixa coesão é uma gaveta de tranqueiras, onde funções sem relação convivem só porque alguém precisou colocá-las em algum lugar. Acoplamento mede o quanto um módulo depende de outros. Alto acoplamento significa que mexer em um módulo obriga a mexer em vários; baixo acoplamento significa que cada módulo pode mudar sozinho, atrás de uma fronteira estável.
A meta clássica, repetida por Pressman e Sommerville, é buscar alta coesão e baixo acoplamento. As duas propriedades andam juntas: quando cada módulo concentra uma responsabilidade (alta coesão), naturalmente ele precisa saber menos sobre os outros (baixo acoplamento). O diagrama abaixo contrasta um arranjo ruim e um bom, e mostra como os dois eixos se relacionam.
flowchart LR
subgraph Ruim["Baixa coesão + Alto acoplamento"]
R1[Módulo A] --- R2[Módulo B]
R1 --- R3[Módulo C]
R2 --- R3
R2 --- R4[Módulo D]
R1 --- R4
R3 --- R4
end
subgraph Bom["Alta coesão + Baixo acoplamento"]
B1[Módulo A] --> B2[Módulo B]
B3[Módulo C] --> B2
end
style Ruim fill:#f8d7da,stroke:#842029
style Bom fill:#d1e7dd,stroke:#0f5132
style R1 fill:#f5c2c7,stroke:#842029
style R2 fill:#f5c2c7,stroke:#842029
style R3 fill:#f5c2c7,stroke:#842029
style R4 fill:#f5c2c7,stroke:#842029
style B1 fill:#a3cfbb,stroke:#0f5132
style B2 fill:#a3cfbb,stroke:#0f5132
style B3 fill:#a3cfbb,stroke:#0f5132
Por que isso importa tanto para a manutenção? Lembre-se de que software não se desgasta, ele se deteriora por mudança. Cada alteração é um risco. Num sistema de baixo acoplamento, esse risco fica contido: você mexe em um módulo com a confiança razoável de que não vai partir os demais, porque eles só conheciam sua fronteira estável, não seus detalhes internos. Num sistema de alto acoplamento, uma pequena mudança se propaga como onda — o famoso efeito cascata — e o custo de qualquer melhoria cresce até o sistema virar aquilo que ninguém quer tocar. A tabela sintetiza os dois conceitos:
| Conceito | O que mede | Estado desejável | Efeito na manutenção |
|---|---|---|---|
| Coesão | O quanto os elementos de um módulo pertencem a um mesmo propósito | Alta | Módulos fáceis de entender e reusar |
| Acoplamento | O quanto um módulo depende de outros para funcionar | Baixo | Mudanças localizadas, sem efeito cascata |
Alto e baixo acoplamento em código
Nada fixa esses conceitos como vê-los no código. Considere um serviço de pedidos que precisa notificar o cliente. Na primeira versão, ele instancia diretamente uma classe concreta de envio de e-mail. O acoplamento é alto: o serviço conhece a classe concreta e depende dela.
class NotificadorEmail {
void enviar(String destino, String mensagem) {
// envia e-mail...
}
}
class ServicoPedido {
final NotificadorEmail notificador = NotificadorEmail(); // dependência direta
void finalizar(String cliente) {
// ... processa o pedido
notificador.enviar(cliente, 'Seu pedido foi confirmado.');
}
}Repare no problema. Se amanhã quisermos notificar por SMS, ou por push, teremos de abrir e alterar ServicoPedido. Além disso, testar essa classe isoladamente é difícil, porque ela sempre carrega o envio real de e-mail junto. O serviço, que deveria cuidar de pedidos, está amarrado a um detalhe de infraestrutura. Agora compare com uma versão de baixo acoplamento, obtida com injeção de dependência por interface.
abstract class Notificador {
void enviar(String destino, String mensagem);
}
class NotificadorEmail implements Notificador {
@override
void enviar(String destino, String mensagem) {
// envia e-mail...
}
}
class ServicoPedido {
final Notificador notificador; // depende da abstração, não do concreto
ServicoPedido(this.notificador); // recebe de fora
void finalizar(String cliente) {
// ... processa o pedido
notificador.enviar(cliente, 'Seu pedido foi confirmado.');
}
}A mudança parece pequena, mas é profunda. ServicoPedido agora depende apenas do contrato Notificador, não de uma implementação. Podemos passar um notificador de e-mail, de SMS ou um dublê de teste sem tocar uma linha do serviço. A dependência foi invertida: em vez de o serviço escolher seu colaborador, ele o recebe pronto. Essa é a essência do baixo acoplamento — programar contra abstrações estáveis, e é também a semente de vários princípios de bom projeto e de padrões que veremos a seguir.
Princípios de bom projeto
Coesão e acoplamento não são regras isoladas; eles se apoiam em princípios mais amplos que a literatura consolidou. O primeiro é a separação de interesses: cada parte do sistema deve tratar de uma preocupação, e preocupações diferentes não devem se misturar — foi exatamente o que MVC e as camadas fizeram em escala arquitetural. O segundo é o ocultamento de informação, formulado desde os trabalhos clássicos de projeto modular: um módulo deve esconder suas decisões internas atrás de uma interface, expondo o que faz e ocultando como faz, de modo que mudanças internas não vazem para fora.
Há ainda a busca por abstração — trabalhar no nível de conceito certo, escondendo detalhes que não importam naquele nível — e a modularidade, que é a soma de tudo isso: um sistema feito de peças substituíveis. Martin, em sua obra sobre código limpo, insiste que essas ideias não são luxo estético, e sim economia: um projeto que respeita esses princípios custa menos para evoluir. Sommerville resume bem ao dizer que o objetivo de todo bom projeto é acomodar a mudança, porque a mudança é a única certeza na vida de um software.
Diante de qualquer módulo, pergunte: “se um requisito mudar, quantos lugares eu terei de alterar?” e “consigo explicar a responsabilidade deste módulo em uma frase?”. Respostas ruins a essas perguntas denunciam baixo coesão ou alto acoplamento antes mesmo de qualquer métrica.
O que são padrões de projeto
Se você reparar, muitos problemas de projeto se repetem: como criar objetos sem amarrar o código à classe concreta, como permitir que um objeto avise outros quando muda, como envolver um comportamento variável atrás de uma interface estável. Ao longo dos anos, engenheiros perceberam que boas soluções para esses problemas recorrentes também se repetiam — e resolveram catalogá-las. Um padrão de projeto (design pattern) é justamente isso: uma solução comprovada e reutilizável para um problema recorrente de projeto, descrita de forma que você possa adaptá-la ao seu contexto.
Padrões não são código pronto para copiar; são receitas de estrutura, descritas por um nome, um problema que resolvem, uma solução em termos de classes e responsabilidades, e as consequências de adotá-la. O maior valor talvez seja o vocabulário: quando um colega diz “aqui usei um Observador” ou “isto é uma Estratégia”, ele comunica uma ideia inteira de projeto numa palavra. Freeman, no livro que aborda esses padrões de forma acessível, defende que aprendê-los é aprender a pensar como projetistas experientes. Não se preocupe em decorá-los agora; queira apenas entender que a injeção por interface que você acabou de ver já é, em espírito, o que muitos padrões formalizam. Voltaremos a eles adiante com o cuidado que merecem.
A outra face: gerência de projetos de software
Trocamos de perna. Um projeto de software impecável no papel fracassa se ninguém cuidar de prazo, custo, pessoas e riscos. A gerência de projetos é a atividade de planejar, organizar e acompanhar o trabalho para que o software seja entregue no prazo, dentro do orçamento e com a qualidade combinada. Sommerville observa que gerenciar software tem dificuldades próprias: o produto é intangível — não dá para ver “metade de um software” como se vê metade de uma ponte —, os processos variam muito entre organizações e o trabalho depende fortemente de pessoas. Isso torna o acompanhamento mais difícil e exige atenção redobrada do gerente.
Estimar esforço e prazo
O planejamento começa por uma pergunta desconfortável: quanto isto vai custar e quanto tempo vai levar? Estimar é responder a isso antes de ter certeza — por isso estimativa é difícil e frequentemente erra. Pressman trata do tema porque estimativas irreais são causa comum de fracasso: prometem-se prazos que nascem impossíveis. A boa prática não é acertar na mosca, mas estimar com método e tratar a estimativa como aproximação sujeita a revisão. Estima-se o esforço, normalmente em pessoas-tempo, e dele deriva-se o prazo, lembrando que os dois não são intercambiáveis: dobrar as pessoas não corta o prazo pela metade, porque comunicação e coordenação crescem com a equipe. Boas estimativas apoiam-se em decompor o trabalho em partes menores, usar a experiência de projetos passados e revisar os números à medida que se aprende.
Marcos, cronograma e a estrutura analítica de trabalho
Com uma noção de esforço, monta-se o planejamento. Duas ferramentas o sustentam. A estrutura analítica de trabalho (EAP, do inglês WBS) decompõe o projeto inteiro em pacotes de trabalho cada vez menores, até chegar a unidades que se pode estimar e atribuir a alguém. É a mesma lógica de decomposição do projeto técnico, aplicada agora à gestão: dividir para conquistar e para enxergar. Sobre essa decomposição, definem-se os marcos — pontos verificáveis no tempo em que se pode dizer, sem ambiguidade, que algo ficou pronto. Um marco não é uma atividade; é uma âncora de verificação, algo que permite ao gerente saber se o projeto anda no ritmo previsto. A tabela abaixo distingue conceitos que estudantes costumam misturar:
| Conceito | O que é | Papel no planejamento |
|---|---|---|
| Estrutura analítica (EAP) | Decomposição hierárquica do trabalho em pacotes | Torna o projeto estimável e distribuível |
| Atividade | Trabalho que consome tempo e recursos | Unidade de execução e acompanhamento |
| Marco | Ponto verificável de conclusão | Âncora para medir progresso |
| Cronograma | Ordenação das atividades no tempo | Mostra dependências e caminho até os marcos |
Gestão de riscos
Nenhum plano sobrevive intacto ao contato com a realidade, e o bom gerente sabe disso. A gestão de riscos é o esforço deliberado de antecipar o que pode dar errado antes que dê. Sommerville a descreve como um ciclo: primeiro identificam-se os riscos — de tecnologia, de pessoas, de requisitos, de prazo —; depois se analisa cada um quanto à probabilidade de ocorrer e ao impacto que causaria; em seguida se planejam respostas, seja para evitar o risco, seja para reduzi-lo, seja para ter um plano B; e, por fim, monitoram-se os riscos ao longo do projeto, porque eles mudam. A diferença entre uma equipe madura e uma amadora costuma estar aqui: a amadora é surpreendida pelo que a madura já tinha previsto e preparado. Repare que gestão de riscos não elimina a incerteza — ela a torna administrável, transformando surpresas em contingências planejadas.
Um risco só existe enquanto é incerto. Quando um risco se concretiza, ele deixa de ser risco e passa a ser um problema. Todo o valor da gestão de riscos está em agir na janela em que ainda é possível escolher a resposta, e não apenas reagir ao estrago.
Como as duas faces se conectam
Note que técnica e gerência não são mundos separados: elas se alimentam. A decomposição arquitetural em módulos coesos e pouco acoplados é o que torna a estrutura analítica de trabalho crível, porque módulos independentes podem ser estimados e distribuídos com menos surpresas. Um projeto técnico ruim, cheio de acoplamento, sabota o planejamento: tudo depende de tudo, nada fica pronto isoladamente, e os marcos escorregam. Boas decisões de projeto são, portanto, também decisões de gerência — e é por isso que trato as duas na mesma conversa.
Síntese
Quero que você retenha três ideias. A primeira é que projeto é a ponte entre o problema e a solução: a análise diz o que, o projeto diz como, e essa passagem exige decisões conscientes sobre arquitetura, módulos e princípios. A segunda é que alta coesão e baixo acoplamento são o coração do bom projeto, porque contêm o custo da mudança — e você viu, em código, como a injeção de dependência por interface troca uma amarra rígida por uma fronteira flexível. A terceira é que nenhum projeto vive só de técnica: estimar, planejar com marcos e uma estrutura analítica, e gerir riscos são atividades que decidem tanto quanto o desenho das classes. Chegue à aula com essas três ideias assentadas.
Para consolidar antes da aula: pegue um sistema pequeno que você já escreveu e tente descrevê-lo em módulos, apontando onde há alto acoplamento. Depois, escolha um desses acoplamentos e imagine como uma interface o afrouxaria. Se você conseguir explicar em voz alta por que essa mudança facilitaria a manutenção, entendeu o essencial deste módulo.