Trilha 2 · Modularização e complexidade · ~26 min · pleno/sênior

Todo projeto que já auditei tinha um arquivo com um destes nomes: utils.ts, helpers.ts, common.ts, shared.service.ts. E todos eles eram a mesma coisa: um sótão. O lugar onde as pessoas jogam o que não sabem onde colocar. Abri o UtilsService de um sistema de saúde uma vez e ele tinha, lado a lado, no mesmo arquivo: uma função que formatava CPF, uma que calculava idade a partir da data de nascimento, uma que fazia retry de chamada HTTP, uma que convertia Celsius em Fahrenheit e uma que gerava o PDF da receita médica. Cinco responsabilidades que não têm absolutamente nada a ver uma com a outra, convivendo só porque nenhuma "tinha casa".

Esse arquivo é o retrato da baixa coesão. E o mais interessante: aquele time era obcecado por acoplamento. Eles tinham regras de lint contra imports cruzados, discutiam data coupling em code review — mas o sistema ainda era difícil de mudar. Por quê? Porque acoplamento e coesão são duas faces da mesma moeda, e cuidar de uma só não resolve. Você pode ter módulos lindamente desacoplados por fora e apodrecidos por dentro, cada um sendo um UtilsService gigante. Esta aula é sobre a face que quase todo mundo negligencia.

O que é coesão (e por que é o outro lado da moeda)

Coesão é o grau em que os elementos dentro de um módulo pertencem uns aos outros. Um módulo coeso tem uma razão de existir clara; tudo dentro dele contribui para essa razão. Um módulo não-coeso é um ajuntamento de coisas que só estão juntas por acidente histórico.

A relação com acoplamento (aula As dimensões do acoplamento: força, distância e tempo) é o insight que amarra a Trilha 2:

  • Acoplamento mede as relações entre módulos (queremos fraco).
  • Coesão mede as relações dentro de um módulo (queremos forte).

E aqui está o ponto genial que quase todo curso perde: as duas se movem juntas. Quando você tem baixa coesão (coisas não-relacionadas no mesmo módulo), você quase inevitavelmente cria alto acoplamento — porque, se conceitos que mudam por motivos diferentes estão no mesmo lugar, mudá-los arrasta dependências para todo lado, e quem precisa de uma dessas coisas acaba dependendo de todas. Inversamente, alta coesão (o que muda junto está junto) naturalmente reduz acoplamento: quem depende do seu módulo depende de uma coisa coerente, não de um saco de responsabilidades.

A lei que governa a Trilha 2: maximize a coesão dentro dos módulos e minimize o acoplamento entre eles. As duas metas se reforçam. Otimizar uma sozinha (como o time da abertura, obcecado por acoplamento) deixa metade do problema em pé.

Coesão também é diretamente carga cognitiva (aula Complexidade essencial vs. acidental e carga cognitiva): quando o que muda junto está junto, você entende uma mudança olhando um lugar. Quando está espalhado (baixa coesão), você caça pedaços em cinco arquivos para montar o quadro — a carga explode.

A escala de coesão (do pior ao melhor)

A engenharia de software clássica (Larry Constantine, refinada por Yourdon & Constantine, e revisitada por Vlad Khononov no contexto moderno) organiza coesão numa escala. Vamos percorrê-la do pior ao melhor, com código no FakeFlix. Você não precisa decorar os nomes — precisa reconhecer o cheiro de cada nível.

Nível 1 — Coesão coincidental (o pior): o UtilsService

Elementos juntos por puro acaso, sem relação alguma.

// ❌ coesão coincidental: nada aqui tem a ver com nada
@Injectable()
export class UtilsService {
  formatCpf(cpf: string): string { /* ... */ }
  calculateAge(birthDate: Date): number { /* ... */ }
  retryHttp<T>(fn: () => Promise<T>, times: number): Promise<T> { /* ... */ }
  celsiusToFahrenheit(c: number): number { /* ... */ }
  generatePrescriptionPdf(data: PrescriptionData): Buffer { /* ... */ }
}

Este é o sótão da abertura. O problema não é estético: qualquer módulo que precise de uma dessas funções passa a depender do UtilsService inteiro (stamp/common coupling da aula As dimensões do acoplamento: força, distância e tempo). Mudar o gerador de PDF pode, por um import bobo, arrastar o módulo que só queria formatar CPF. Coesão coincidental é uma fábrica de acoplamento acidental.

Nível 2 — Coesão lógica: agrupadas por "categoria", não por propósito

Elementos que fazem coisas da mesma categoria genérica, mas para propósitos diferentes.

// ❌ coesão lógica: "tudo que valida" junto — mas validam coisas de domínios diferentes
@Injectable()
export class ValidationService {
  validateCreditCard(card: Card): boolean { /* regra de BILLING */ }
  validateVideoFormat(file: File): boolean { /* regra de CONTENT */ }
  validatePassword(pwd: string): boolean { /* regra de IDENTITY */ }
}

Parece organizado ("juntei todas as validações!"), mas é uma armadilha: agrupou por tipo de operação (validar) em vez de por domínio. A validação de cartão muda quando billing muda; a de vídeo, quando content muda. Elas não têm motivo comum para mudar — só a palavra "validate" em comum. Resultado: os três domínios acoplam-se a esse serviço, e uma mudança em billing toca um arquivo compartilhado com content e identity.

Nível 3 — Coesão temporal: juntas porque acontecem "ao mesmo tempo"

Elementos agrupados porque executam no mesmo momento do ciclo de vida.

// ❌ coesão temporal: "coisas que rodam no startup" — mas são de domínios distintos
@Injectable()
export class StartupService {
  onModuleInit() {
    this.connectDatabase();       // infra
    this.warmCatalogCache();      // content
    this.loadFeatureFlags();      // plataforma
    this.reconcilePendingCharges(); // billing
  }
}

Só estão juntas porque rodam no boot. Mas reconcilePendingCharges é assunto de billing e deveria mudar com billing; warmCatalogCache é de content. Juntá-las cria um ponto onde quatro domínios se cruzam. O gancho aqui é com o acoplamento temporal da aula As dimensões do acoplamento: força, distância e tempo: coesão temporal frequentemente esconde acoplamento temporal (ordem de execução virando regra implícita).

Nível 4 — Coesão comunicacional / sequencial: operam sobre os mesmos dados

Elementos que trabalham sobre o mesmo dado, um usando a saída do outro. Já é bom — estamos subindo a escala.

// 🟡 coesão sequencial: cada passo alimenta o próximo, tudo sobre o mesmo vídeo
@Injectable()
export class VideoIngestionService {
  async ingest(raw: RawVideo): Promise<PublishedVideo> {
    const validated = this.validate(raw);          // saída →
    const transcoded = await this.transcode(validated); // → entrada
    const thumb = await this.generateThumbnail(transcoded);
    return this.persist(transcoded, thumb);
  }
}

Aqui tudo pertence junto: é o pipeline de ingestão de um vídeo, cada passo consome o anterior, tudo sobre o mesmo dado e o mesmo propósito. Coesão alta e legítima.

Nível 5 — Coesão funcional (o alvo): uma responsabilidade, um motivo de existir

Todos os elementos contribuem para uma única tarefa bem definida.

// ✅ coesão funcional: existe para UMA coisa — gerir o ciclo de vida da assinatura
@Injectable()
export class SubscriptionService {
  activate(userId: string): Promise<void> { /* ... */ }
  cancel(userId: string): Promise<void> { /* ... */ }
  suspendForNonPayment(userId: string): Promise<void> { /* ... */ }
  isActive(userId: string): Promise<boolean> { /* ... */ }
}

Tudo aqui é sobre o ciclo de vida de uma assinatura. Muda por um motivo (as regras de assinatura mudaram), a pedido de um ator (o negócio de billing). Quem depende dele depende de um conceito coerente. Este é o destino de todo módulo: funcional.

Guarde a escala como um "cheiro-ômetro": coincidental (aleatório) → lógica (mesma categoria) → temporal (mesmo momento) → sequencial/comunicacional (mesmo dado) → funcional (mesmo propósito). Quanto mais alto, mais o módulo "pertence a si mesmo".

O teste operacional definitivo

Escalas são úteis para diagnosticar, mas na hora de decidir "isso fica junto ou separado?" você precisa de um teste rápido. É este, e ele vem do Princípio da Responsabilidade Única bem entendido:

"Isso muda pelo mesmo motivo, a pedido do mesmo ator?" Se sim, é coeso — fica junto. Se não, são responsabilidades diferentes — separe.

O detalhe que muda tudo é a palavra ator. O SRP costuma ser mal ensinado como "uma classe faz uma coisa" (vago, leva à fragmentação). A formulação precisa de Uncle Bob é: "um módulo deve ter uma, e apenas uma, razão para mudar" — e razão para mudar significa um ator/stakeholder do negócio que pede a mudança. Volte ao ValidationService do Nível 2: validateCreditCard muda quando o pessoal de billing muda as regras de pagamento; validateVideoFormat muda quando o pessoal de content muda formatos aceitos. Atores diferentes → motivos de mudança diferentes → não deveriam estar na mesma classe. O teste do ator os separa na hora.

Aplique ao UtilsService da abertura: formatCpf muda a pedido de regras de identidade; generatePrescriptionPdf muda a pedido do time médico/clínico; retryHttp muda a pedido de decisões de infraestrutura. Três (ou cinco) atores. O teste grita "separe".

O par do SRP: o Princípio da Proximidade Comum (Common Closure)

O SRP tem um irmão em nível de pacote/módulo, o Common Closure Principle: "classes que mudam pelos mesmos motivos, no mesmo momento, devem estar no mesmo pacote." É a versão "para cima" da coesão. É por isso que o FakeFlix agrupa por domínio (billing/, content/, identity/) e não por camada técnica (controllers/, services/): quando billing muda, você quer tocar um lugar (billing/), não caçar pedaços de billing espalhados em controllers/, services/ e repositories/. Organizar por domínio é coesão de módulo aplicada — o que muda junto (todo o billing) fica junto.

Código: refatorando o UtilsService por coesão

Vamos desmontar o sótão aplicando o teste do ator. Cada função vai para o módulo cujo ator a governa.

Antes (baixa coesão, um ponto de acoplamento para todos):

// ❌ shared/utils.service.ts — cinco atores, uma classe
@Injectable()
export class UtilsService {
  formatCpf(cpf: string): string { /* ... */ }
  calculateAge(birthDate: Date): number { /* ... */ }
  retryHttp<T>(fn: () => Promise<T>, times: number): Promise<T> { /* ... */ }
  celsiusToFahrenheit(c: number): number { /* ... */ }
  generatePrescriptionPdf(data: PrescriptionData): Buffer { /* ... */ }
}

Depois (cada coisa na casa do seu ator):

// ✅ identity/core/value-object/cpf.ts — ator: regras de identidade
export class Cpf {
  constructor(private readonly value: string) { /* valida no construtor */ }
  formatted(): string { /* ... */ }   // formatação vive JUNTO do conceito que ela serve
}

// ✅ identity/core/service/user-age.policy.ts — ator: identidade (idade mínima etc.)
export function calculateAge(birthDate: Date): number { /* ... */ }

// ✅ shared/infra/http/resilient-http.client.ts — ator: infraestrutura
export class ResilientHttpClient {
  async withRetry<T>(fn: () => Promise<T>, times: number): Promise<T> { /* ... */ }
}

// (celsiusToFahrenheit não pertencia a NENHUM domínio deste sistema — era morto. Removido.)

// ✅ clinical/core/prescription/prescription-pdf.renderer.ts — ator: time clínico
export class PrescriptionPdfRenderer {
  render(data: PrescriptionData): Buffer { /* ... */ }
}

Repare no efeito colateral maravilhoso: o acoplamento caiu junto. Antes, o módulo clínico e o de identidade dependiam do mesmo UtilsService (e transitivamente um do outro). Agora, cada um depende só do que lhe diz respeito. Aumentei a coesão e o acoplamento diminuiu de graça — porque são a mesma moeda. E note que formatCpf virou método do value object Cpf: a formatação está agora junto do conceito que ela serve, o que é coesão levada ao objeto (a lógica que pertence ao dado vive com o dado).

Um caso mais sutil: coesão dentro de um módulo grande

Coesão não é só sobre o UtilsService. Um módulo de domínio legítimo pode ficar internamente incoeso. Veja um BillingService que cresceu demais:

// 🟡 começou coeso, mas acumulou responsabilidades de ATORES diferentes
@Injectable()
export class BillingService {
  // ator: regras de ciclo de assinatura
  activate() {} cancel() {} suspendForNonPayment() {}
  // ator: integração com o gateway de pagamento (muda quando troca de PSP)
  chargeCard() {} refund() {} handleGatewayWebhook() {}
  // ator: fiscal/contábil (muda quando a legislação muda)
  issueInvoice() {} calculateTax() {}
}

Três atores num serviço só. O teste os separa em três serviços coesos, ainda dentro de billing/ (Common Closure: continuam no mesmo módulo, porque juntos formam "billing"; mas cada um muda por seu motivo):

// ✅ billing/core/service/subscription.service.ts    → ciclo de vida
// ✅ billing/core/service/payment-gateway.service.ts → integração PSP
// ✅ billing/core/service/invoicing.service.ts       → fiscal

Isto é coesão em duas escalas ao mesmo tempo: entre módulos (billing é um todo coeso — Common Closure) e dentro do módulo (cada serviço tem um ator só — SRP). As duas escalas trabalhando juntas.

Coesão alta demais também é problema: a fragmentação

Aqui é onde a aula vira "de verdade", porque quase ninguém fala disto. Se você aplicar "um ator, um módulo" com zelo cego, você chega no outro extremo: fragmentação. Você quebra tudo em micro-arquivos de uma função, e para entender qualquer fluxo você salta por 30 arquivos. Isso não é alta coesão — é a ilusão dela.

// ❌ fragmentação: "SRP levado ao absurdo", cada linha vira uma classe
class SubscriptionStatusChecker { check(s: Subscription): boolean {} }
class SubscriptionPeriodValidator { validate(s: Subscription): boolean {} }
class SubscriptionActiveDecider {
  constructor(private c: SubscriptionStatusChecker, private v: SubscriptionPeriodValidator) {}
  decide(s: Subscription) { return this.c.check(s) && this.v.validate(s); }
}
// ...para responder "a assinatura está ativa?" você navega 3 classes

O que deu errado? A definição de "muda pelo mesmo motivo" foi levada fina demais. status e período de uma assinatura mudam pelo mesmo motivo (as regras de "assinatura ativa" mudaram) e a pedido do mesmo ator (billing). Então eles pertencem juntos — no SubscriptionService. Separá-los criou acoplamento (agora SubscriptionActiveDecider depende de dois colaboradores para uma decisão trivial) para "ganhar" uma coesão que era falsa.

Este é o ponto de equilíbrio da Trilha 2 inteira: a coesão máxima não é o menor pedaço possível; é o pedaço que agrupa exatamente o que muda junto — nem mais, nem menos. Fragmentar além disso não aumenta coesão, aumenta acoplamento (mais peças que precisam se conhecer) e carga cognitiva (mais saltos). A moeda de novo: exagerar na "coesão" (fragmentar) piora o acoplamento. Coesão e acoplamento não são um dial que você gira só para um lado — são um equilíbrio.

A calibragem certa: junte o que muda pelo mesmo motivo a pedido do mesmo ator (nem menos — senão baixa coesão; nem mais — senão fragmentação). O tamanho ideal de um módulo é "cabe na cabeça e tem um motivo só para mudar".

Antes → Depois

Antes Depois
UtilsService com 5 atores Cada função na casa do seu ator
Todo mundo depende do sótão Cada módulo depende só do que lhe diz respeito
Acoplamento alto "de brinde" Acoplamento caiu junto com a subida da coesão
Organizado por camada (services/) Organizado por domínio (Common Closure)
Ou fragmentado em micro-classes Agrupado por "muda junto" — cabe na cabeça

Tradeoffs e armadilhas

  • Coesão e acoplamento se equilibram, não se maximizam isoladamente. Empurrar coesão ao extremo (fragmentar) sobe o acoplamento; tolerar baixa coesão (o sótão) também sobe o acoplamento. O ótimo é um vale entre os dois, definido pelo teste do ator.
  • "Ator" às vezes é ambíguo. Em domínios novos você ainda não sabe quem são os atores nem o que muda junto — a Linguagem Ubíqua (aula DDD estratégico + Arquitetura Hexagonal no FakeFlix) ainda está se formando. Nesses casos, comece mais agrupado (menos arquivos) e separe quando a evidência de "isto muda por motivo diferente" aparecer (aula Por que não existe arquitetura perfeita: evolua com evidência). É mais barato dividir depois do que juntar de volta.
  • DRY pode destruir coesão. "Não repita código" leva gente a criar um helper compartilhado só porque duas linhas se parecem — mesmo que sirvam a atores diferentes. Duas regras que hoje são idênticas mas mudam por motivos diferentes devem ser duplicadas, não unificadas: unificá-las cria baixa coesão e acoplamento entre domínios. DRY é sobre conhecimento único, não sobre texto parecido.
  • Coesão não some no assíncrono. Ao quebrar um fluxo em eventos (aula Subdomínios e comunicação assíncrona (BullMQ): mensagens vs. eventos), cuidado para não espalhar uma responsabilidade coesa por cinco consumidores. Coesão vale para o fluxo lógico, não só para o arquivo.

Cola mental

  • Coesão = o quanto os elementos de um módulo pertencem uns aos outros (queremos forte). É o par do acoplamento (aula As dimensões do acoplamento: força, distância e tempo): as duas se movem juntas.
  • Baixa coesão causa acoplamento; alta coesão reduz acoplamento de graça. A lei da Trilha 2: coesão forte dentro, acoplamento fraco entre.
  • Escala (pior→melhor): coincidental → lógica → temporal → sequencial → funcional.
  • Teste operacional: "muda pelo mesmo motivo, a pedido do mesmo ator?" Sim = junto; não = separe. (SRP bem entendido: uma razão/ator para mudar.)
  • Organize por domínio, não por camada (Common Closure Principle).
  • Fragmentar demais é o outro extremo: sobe o acoplamento e a carga cognitiva. O tamanho certo é "o que muda junto, nem mais nem menos".

Referências para Aprofundamento

A escala e o critério

Coesão como propriedade de módulo

Exercícios

Exercício 1 — Classifique o nível de coesão

Para cada módulo, diga o nível na escala (coincidental / lógica / temporal / sequencial / funcional) e justifique: (a) um DateHelper com formatDate, parseDate, addBusinessDays, isWeekend; (b) um ManagerService com createUser, chargePayment, uploadVideo; (c) um OnboardingService que, em sequência, cria o usuário, cria a assinatura trial e envia o e-mail de boas-vindas; (d) um AppBootstrapService que no start conecta no banco, aquece cache e carrega feature flags; (e) um PricingService com calculatePrice, applyDiscount, applyTax — todos usados para precificar um plano.

Ver resposta

✓ Resposta: (a) Funcional. Tudo é sobre manipular datas — uma responsabilidade coerente, um ator (regras de data/calendário). É um utilitário legítimo (diferente do UtilsService, que misturava domínios). (b) Coincidental (ou lógica, no melhor caso). createUser, chargePayment, uploadVideo são de três domínios (identity, billing, content) sem relação; o nome "Manager" é o sótão. Separe por domínio. (c) Sequencial. Cada passo alimenta o próximo, tudo sobre o mesmo fluxo de onboarding de um usuário; coesão alta e legítima. (Nuance: se os passos passarem a mudar por atores muito diferentes, considere torná-los eventos — aula Subdomínios e comunicação assíncrona (BullMQ): mensagens vs. eventos.) (d) Temporal. Só estão juntas porque rodam no boot; conectam domínios distintos. Cheiro de acoplamento temporal escondido. (e) Funcional/sequencial. Todos servem a uma tarefa única (precificar um plano), operando sobre o mesmo dado; alta coesão.

Exercício 2 — Aplique o teste do ator

Um NotificationService tem: sendPasswordResetEmail (identity), sendPaymentFailedEmail (billing), sendNewEpisodeEmail (content). Ele viola a coesão? Aplique o teste do ator e decida se separa — e, se separar, para onde vão os métodos. Cuidado: há uma parte que deveria ser compartilhada. Qual?

Ver resposta

✓ Resposta: Sim, viola a coesão: os três métodos mudam a pedido de atores diferentes (o conteúdo/gatilho do e-mail de reset muda com identity; o de pagamento com billing; o de episódio com content). Pelo teste do ator, separe: sendPasswordResetEmail → identity/, sendPaymentFailedEmail → billing/, sendNewEpisodeEmail → content/ (cada um dispara o e-mail que lhe diz respeito, tipicamente via um evento — aula Subdomínios e comunicação assíncrona (BullMQ): mensagens vs. eventos). A parte que deve ser compartilhada: o mecanismo de envio (falar com o provedor de e-mail: SMTP/SendGrid, retry, templating base) é infraestrutura com um ator só (infra/plataforma) e alta coesão própria — vive em shared/infra/email/ como um EmailSender que os três domínios usam. Ou seja: separa-se o conteúdo de negócio (por domínio) do mecanismo de entrega (compartilhado e coeso). Confundir os dois é o erro; separá-los corretamente resolve coesão sem duplicar infraestrutura.

Exercício 3 — Detecte a fragmentação

O código abaixo foi "modularizado". Diagnostique o problema à luz do equilíbrio coesão/acoplamento e proponha a correção:

class EmailFormatValidator { isValid(e: string): boolean {} }
class EmailDomainValidator { isValid(e: string): boolean {} }
class EmailLengthValidator { isValid(e: string): boolean {} }
class EmailValidator {
  constructor(private f: EmailFormatValidator, private d: EmailDomainValidator, private l: EmailLengthValidator) {}
  validate(e: string) { return this.f.isValid(e) && this.d.isValid(e) && this.l.isValid(e); }
}
Ver resposta

✓ Resposta: Fragmentação. As três "validações" (formato, domínio, tamanho) de um e-mail mudam pelo mesmo motivo (as regras do que é um e-mail aceitável mudaram) e a pedido do mesmo ator. Logo, pertencem juntas — não são responsabilidades distintas. Separá-las em três classes não aumentou a coesão (que já seria alta num só lugar); criou acoplamento (EmailValidator agora depende de três colaboradores para uma checagem trivial) e carga cognitiva (para entender a validação você navega quatro classes). Correção: uma única função/classe coesa validateEmail(e) que faz as três checagens internamente. Regra: SRP é sobre "um motivo/ator para mudar", não "uma linha por classe".

Exercício 4 — DRY vs. coesão

Billing tem uma regra "aplicar 10% de acréscimo se pagamento atrasado" e Content tem uma regra "aplicar 10% de acréscimo em conteúdo premium". Hoje o código das duas é idêntico. Um colega quer extrair para um apply10PercentSurcharge compartilhado. Você concorda? Justifique com o teste do ator e explique o risco.

Ver resposta

✓ Resposta: Não concordo em extrair um helper compartilhado. Aplicando o teste do ator: a regra de billing muda a pedido do ator de billing (política de juros/atraso), e a de content muda a pedido do ator de content (política de precificação premium). São atores e motivos de mudança diferentes — o fato de o código ser hoje idêntico é coincidência, não conhecimento único. Risco de unificar: no dia em que billing precisar mudar para 12% e content continuar em 10%, o helper compartilhado vira um campo de batalha (parâmetros, flags, ifs por origem — control coupling da aula As dimensões do acoplamento: força, distância e tempo), e uma mudança em billing arrisca quebrar content. Você teria criado baixa coesão (dois atores num lugar) e acoplamento entre domínios que não deveriam se conhecer. Aqui, a duplicação é a decisão correta: DRY é sobre não duplicar conhecimento (uma regra única do negócio), não sobre não duplicar texto que por acaso se parece. Cada domínio mantém sua própria regra.

Exercício 5 — Conecte as moedas

Explique, com um exemplo próprio, por que um UtilsService de baixa coesão causa alto acoplamento entre módulos que, conceitualmente, não deveriam se conhecer. Qual tipo de acoplamento da aula As dimensões do acoplamento: força, distância e tempo aparece?

Ver resposta

✓ Resposta: Exemplo: um UtilsService contém formatMoney (usado por billing) e slugifyTitle (usado por content). O módulo billing importa UtilsService por causa de formatMoney; o módulo content importa UtilsService por causa de slugifyTitle. Agora os dois dependem da mesma classe — e, transitivamente, do código um do outro (se slugifyTitle importar algo pesado de content, billing carrega junto; qualquer mudança na assinatura da classe afeta ambos). Billing e content, que conceitualmente não deveriam se conhecer, ficaram acoplados por um intermediário acidental. O tipo de acoplamento é common coupling (dependência de um elemento compartilhado comum) tendendo a stamp coupling (dependem do formato da classe UtilsService). A baixa coesão do sótão materializou o acoplamento: porque coisas não-relacionadas foram postas juntas, quem precisava de uma passou a depender de todas. É a moeda dos dois lados girando — baixa coesão de um lado, alto acoplamento do outro.


Fim da aula Coesão: o outro lado da moeda. Manda "próxima" para a aula Os 10 princípios da arquitetura modular — Os 10 princípios da arquitetura modular (com exemplo aplicado e anti-exemplo para cada princípio, mais código e casos, como você pediu).