Design de software

[Modularização #3] Por que o Common vira um depósito de lixo

A maioria das equipes que fazem modularização acaba criando módulos chamados Common, Core ou Utils.

5 min de leitura
Imagem de capa de [Modularização #3] Por que o Common vira um depósito de lixo

A maioria das equipes que fazem modularização acaba criando módulos chamados Common, Core ou Utils.

Quando decisões como “é usado em vários lugares, então coloque aqui por enquanto” se acumulam, o módulo se torna, sem percebermos, o maior, o que mais muda e aquele de que todos dependem.

Em resumo: chamar um módulo de Common é admitir que ele não tem coesão. “Comum” não é um critério para agrupar código.

Este artigo explica por que o módulo Common desfaz a modularização e resume estruturas de camadas (separação vertical) e de funcionalidades (separação horizontal) para evitar isso.


Por que módulos Common são perigosos?

O problema acontece em três etapas.

Etapa 1: virar um depósito de lixo. Todo “código sem um lugar claro” vai para Common. Um cliente de rede fica ao lado de um formatador de datas, seguido por um botão personalizado. Eles não têm relação alguma.

Etapa 2: dependência universal. Todo módulo de funcionalidade importa Common. Nesse ponto, Common ocupa a posição mais abaixo no grafo, que deveria ser a mais estável.

Etapa 3: recompilar tudo. Como Common é um depósito de lixo, ele muda com mais frequência. O princípio das dependências estáveis do artigo anterior foi completamente invertido. Você muda uma cor de botão e todos os módulos são recompilados.

Quando o módulo de que mais dependem também se torna o que mais muda, as fronteiras entre módulos perdem o sentido.


Estrutura de camadas: dividir verticalmente

O primeiro eixo preventivo é a separação vertical por responsabilidade. Uma estrutura comum de 3 ou 4 camadas é esta.

Camada Conteúdo Exemplo
Feature Módulos de tela/funcionalidade Início, busca, pedidos, configurações
Domain Regras de negócio, modelos e casos de uso Políticas de pedidos, modelos de membros
Core Wrappers finos de tecnologias específicas Rede, armazenamento, logging
Shared Utilitários realmente genéricos Formatadores de datas, extensões de String

A regra é uma só: as dependências fluem apenas de cima para baixo.

Feature pode usar Domain e Core, mas Core não pode conhecer Feature. As Features também não fazem referência direta umas às outras; o target superior do app faz a composição.

As dependências fluem apenas para baixo; este diagrama resume toda a regra de camadas.
As dependências fluem apenas para baixo; este diagrama resume toda a regra de camadas.

A diferença em relação a Common é importante. Core não é um módulo gigante, mas vários módulos separados por tecnologia.

  • Por exemplo, CoreNetwork, CoreStorage e CoreLogging são módulos independentes.
  • Se a funcionalidade de busca usa apenas logging, ela importa somente CoreLogging.

Assim, corrigir o código de rede não recompila o módulo que usava apenas logging.


Estrutura de funcionalidades: dividir horizontalmente

O segundo eixo é a separação horizontal por domínio. Mesmo na mesma camada, Início, Busca e Pedidos devem ser módulos diferentes.

O critério é a pergunta do primeiro artigo: “Este código muda pelo mesmo motivo?”

  • Se mudar a política de busca não exigir mudanças no código de pedidos, são módulos diferentes.
  • Se a tela de pedidos e o caso de uso de pedidos sempre mudam juntos, dividi-los mais verticalmente pode ser exagero.

Ao sobrepor as divisões vertical (camadas) e horizontal (funcionalidades), temos uma grade. Cada módulo real ocupa uma célula, como “Domain da funcionalidade de pedidos” ou “Feature da funcionalidade de busca”.


E se o Common já ficou grande demais?

Não é preciso explodir o Common existente de uma vez. A sequência validada na prática é esta.

  1. Proibir novas entradas: comece estabelecendo a regra de não adicionar código novo ao Common a partir de hoje.
  2. Investigar usos: começando pelo código que mais muda, descubra onde ele é realmente usado.
  3. Encontrar o lugar certo: se for usado por uma única funcionalidade, mova-o para esse módulo; se for um wrapper tecnológico, mova-o para um módulo da família Core.
  4. Renomear o restante: isole apenas o código realmente genérico que sobrar, usando um nome mais específico, como Shared.

É um trabalho que leva meses, mas você consegue ver claramente o escopo de recompilação diminuir a cada etapa.

A limpeza começa não adicionando nada novo.
A limpeza começa não adicionando nada novo.

Quando essa estrutura é necessária e quando ela é exagero?

  • Com 4 ou 5 desenvolvedores ou mais e pelo menos 3 áreas de funcionalidade: vale muito a pena definir regras de camadas.
  • Em um projeto de 1 ou 2 pessoas: começar com duas camadas, Feature/Shared, já basta. Desenhar a grade completa é exagero.
  • Em qualquer escala: recomendamos seguir desde o início a regra de “não criar módulos chamados Common ou Utils”.

É assim que pergunto em entrevistas

Q. Que problemas surgem quando um módulo comum (Common) cresce e como ele deve ser projetado?

O módulo de que todos dependem se torna o que mais muda; por isso, até pequenas alterações exigem uma recompilação completa e uma análise ampla de impacto. O princípio das dependências estáveis foi invertido. Divida-o por responsabilidade — rede, armazenamento e logging — e isole apenas o código realmente genérico em um módulo Shared mínimo.

Q. Explique as regras para dividir uma estrutura de módulos em camadas.

Crie camadas por responsabilidade, como Feature, Domain e Core, e permita dependências em uma única direção, de cima para baixo. Módulos da mesma camada não devem fazer referência direta uns aos outros; conecte-os na camada superior de composição. Com essa regra, o impacto das mudanças fica sempre limitado às camadas acima.


Esse foi o terceiro e último artigo sobre os princípios da modularização. Com limites, direção das dependências e estrutura de camadas, você já tem as ferramentas conceituais necessárias.

A partir do próximo artigo, aplicaremos esses princípios diretamente a projetos iOS, começando por como dividir módulos na prática com Swift Package.