Connascence em .NET: Como Reduzir Acoplamento Sem Perder Velocidade de Entrega
Introdução
Embora seja um conceito antigo, Connascence ainda não é bem compreendida por alguns desenvolvedores. Isso é relevante porque o acoplamento excessivo continua sendo uma das principais causas de fragilidade em sistemas corporativos. Em plataformas modernas como .NET, o desafio não é apenas construir rápido, mas permitir evolução segura ao longo do tempo. O conceito de Connascence oferece um vocabulário técnico para diagnosticar dependências e orientar refatorações com critério arquitetural (PAGE-JONES, 1992).
Neste artigo, exploramos como aplicar Connascence em APIs, serviços e fluxos orientados a eventos em .NET 10, mostrando práticas para deslocar acoplamento de áreas distribuídas para pontos locais e mais controláveis.
Fundamentos de Connascence
Definição e motivação
Connascence descreve o grau de conhecimento compartilhado exigido entre partes de um sistema para que ele funcione corretamente. Quanto maior o conhecimento compartilhado e mais distante ele estiver no sistema, maior o risco de regressão em mudanças futuras (PAGE-JONES, 1992).
Principais tipos de Connascence
No dia a dia com .NET, os tipos mais comuns aparecem como:
- Connascence of Name (CoN): dependência de nomes exatos (métodos, propriedades, campos JSON).
- Connascence of Type (CoT): dependência de tipos e formatos específicos.
- Connascence of Meaning (CoM): dependência de convenções implícitas, sem documentação explícita.
- Connascence of Execution/Timing (CoE/CoTg): dependência de ordem e tempo de execução.
- Connascence of Algorithm (CoA): múltiplos pontos replicando o mesmo algoritmo.
- Connascence of Position (CoP): dependência da posição de argumentos ou campos.
O objetivo não é eliminar toda connascence, mas reduzir suas formas mais nocivas e, quando inevitável, mantê-las em escopos locais e altamente testáveis (FOWLER, 2002).
Connascence em APIs .NET
Exemplo de acoplamento por posição e significado
Um antipattern recorrente é expor APIs com parâmetros posicionais sem semântica explícita:
// Antipattern: alto risco de CoP e CoM
public sealed class PricingService
{
public decimal Calculate(decimal value, decimal discount, decimal tax)
=> (value - discount) * (1 + tax);
}
// Chamada frágil: a ordem errada compila e falha apenas em runtime/lógica
var total = pricing.Calculate(100m, 0.2m, 10m);
A refatoração para um contrato explícito reduz ambiguidades e facilita evolução:
public sealed record class PricingInput
{
public required decimal GrossAmount { get; init; }
public required decimal DiscountAmount { get; init; }
public required decimal TaxRate { get; init; }
}
public sealed class PricingService
{
public decimal Calculate(PricingInput input)
=> (input.GrossAmount - input.DiscountAmount) * (1 + input.TaxRate);
}
var total = pricing.Calculate(new PricingInput
{
GrossAmount = 100m,
DiscountAmount = 10m,
TaxRate = 0.2m
});
O resultado da análise de connascence executada no terminal é apresentado a seguir.
Versionamento e connascence de nome
Alterar nomes de campos em contratos públicos cria CoN distribuída entre provedores e consumidores. Em .NET 10, o caminho mais seguro continua sendo evolução aditiva com DTOs versionados por namespace e serialização explícita, mantendo retrocompatibilidade (NEWMAN, 2021).
namespace Contracts.V1;
public sealed record class OrderDto
{
public required Guid Id { get; init; }
public required decimal TotalAmount { get; init; }
}
namespace Contracts.V2;
public sealed record class OrderDto
{
public required Guid Id { get; init; }
public required decimal TotalAmount { get; init; }
public string? Currency { get; init; }
}
Connascence em Arquiteturas Distribuídas
Eventos e connascence de tempo
Em mensageria, assumir ordem fixa de eventos entre serviços diferentes é uma forma perigosa de CoE/CoTg. Em vez disso, prefira mensagens autoexplicativas, idempotência e processamento orientado a estado, não à sequência estrita (VERNON, 2013).
public sealed record class PaymentReceivedV1
{
public required Guid OrderId { get; init; }
public required decimal Amount { get; init; }
public required DateTimeOffset OccurredAt { get; init; }
public required string EventId { get; init; }
}
public sealed class PaymentProjection
{
private readonly HashSet<string> _processed = [];
public bool TryApply(PaymentReceivedV1 evt)
{
if (!_processed.Add(evt.EventId))
{
return false; // Idempotência evita dependência de timing e repetição
}
// Atualiza estado de forma determinística
return true;
}
}
Contratos canônicos como redutores de acoplamento
Quando vários sistemas trocam dados com semânticas próximas, um modelo canônico reduz CoM e CoN porque explicita linguagem ubíqua e regras de transformação em pontos conhecidos. Esse arranjo costuma funcionar melhor quando combinado com mapeadores dedicados e testes de contrato automatizados (EVANS, 2003).
Estratégias Práticas para Times .NET
1. Medir connascence nas revisões técnicas
Durante code review, questione: esta mudança adiciona dependência de nome, ordem, tipo ou tempo entre módulos distantes? Se sim, reavalie o desenho antes de escalar para produção.
2. Localizar connascence forte dentro do mesmo modulo
Connascence forte é menos danosa quando confinada ao mesmo componente, pois a mudança coordenada ocorre no mesmo ciclo de deploy. Esse princípio alinha com a recomendação de aumentar coesão e reduzir acoplamento transversal (MARTIN, 2017).
3. Automatizar verificações de contrato
Em pipelines .NET, testes de serialização e compatibilidade entre versões previnem regressão silenciosa:
public sealed class ContractCompatibilityTests
{
[Fact]
public void V2_Should_Deserialize_V1_Payload()
{
var v1Json = """
{
"Id": "7a6a16de-31c0-4770-9413-6f6cefcf7a3a",
"TotalAmount": 125.50
}
""";
var dto = JsonSerializer.Deserialize<Contracts.V2.OrderDto>(v1Json);
Assert.NotNull(dto);
Assert.Equal(125.50m, dto!.TotalAmount);
Assert.Null(dto.Currency);
}
}
4. Reduzir connascence de algoritmo com serviços únicos
Quando a mesma regra aparece em vários pontos, centralize em um único serviço de domínio para evitar CoA distribuída e divergência semântica entre times.
Conclusão
Connascence oferece uma lente pragmática para evoluir sistemas .NET com menos regressão e mais previsibilidade. Ao identificar dependências por nome, tipo, significado, tempo e algoritmo, equipes técnicas tomam decisões de design com base em risco real de manutenção, não apenas em preferência estilística.
Em cenários corporativos, combinar contratos canônicos, versionamento aditivo, idempotência e testes de compatibilidade transforma o acoplamento de um problema oculto em um ativo gerenciável. O resultado é uma arquitetura mais resiliente, capaz de crescer sem sacrificar autonomia dos times nem estabilidade de integrações existentes.
Referências
-
PAGE-JONES, Meilir. The practical guide to structured systems design. 2. ed. Englewood Cliffs: Yourdon Press, 1992.
-
EVANS, Eric. Domain-driven design: tackling complexity in the heart of software. Boston: Addison-Wesley, 2003.
-
FOWLER, Martin. Patterns of enterprise application architecture. Boston: Addison-Wesley, 2002.
-
NEWMAN, Sam. Building microservices: designing fine-grained systems. 2. ed. Sebastopol: O'Reilly Media, 2021.
-
VERNON, Vaughn. Implementing domain-driven design. Boston: Addison-Wesley, 2013.
-
MARTIN, Robert C. Clean architecture: a craftsman's guide to software structure and design. Boston: Pearson, 2017.