.NET no capricho

Aprenda a fazer dotnet no capricho: DI nativa, async/await com CancellationToken, IDisposable, records e IOptions para código mais confiável. Leia e aplique.

Avatar de Danrley Pereira
Danrley Pereira
.NET no capricho

Todo projeto .NET começa limpo. Aí entra o prazo, entra o "depois eu arrumo", e seis meses depois você tem um static Helper que faz tudo, um async void que engole exceção e um catch que só existe pra fingir que trata erro. Nada disso é culpa do .NET. A plataforma já te dá quase tudo pronto pra fazer certo. O problema é ignorar o que já vem na caixa.

Imagem de abertura/capa que traduza visualmente a ideia de código .NET organizado e bem estruturado versus gambiarra, sem depender de texto.
Imagem de abertura/capa que traduza visualmente a ideia de código .NET organizado e bem estruturado versus gambiarra, sem depender de texto.

Injeção de dependência: a nativa já resolve

Não precisa de Autofac, Ninject nem de um ServiceLocator caseiro. O Microsoft.Extensions.DependencyInjection cobre 95% dos casos. Registre no lugar certo e deixe o container resolver.

O que trava gente é o tempo de vida. Regra prática: Singleton pra coisa sem estado e cara de criar (HttpClient factory, cache), Scoped pra tudo que segue o request (DbContext), Transient pro resto. O erro clássico é injetar um Scoped dentro de um Singleton — o container reclama, e reclama com razão: você prendeu um objeto de request na vida inteira da aplicação.

Programar contra interface aqui não é purismo; é o que deixa o teste possível sem mock mágico.

async/await de verdade: CancellationToken e chega de async void

async void é uma armadilha. Exceção lançada ali não sobe pro chamador — ela derruba o processo ou some. Só existe um uso legítimo: event handler. Fora disso, é async Task, sempre.

E propague o CancellationToken. Ele não é enfeite de assinatura: é o que faz sua API parar de processar quando o cliente já desistiu. Recebeu no controller, passa pro service, passa pro HttpClient, passa pro EF Core. Um token que morre no primeiro método é um token inútil.

public async Task<Pedido> BuscarAsync(int id, CancellationToken ct)
{
    var pedido = await _db.Pedidos.FindAsync([id], ct);
    return pedido ?? throw new NotFoundException(id);
}

De quebra: nada de .Result ou .Wait(). Isso é deadlock esperando acontecer.

IDisposable e using: solte o que você segura

Conexão, stream, handle de arquivo — tudo que implementa IDisposable precisa ser liberado. E a forma correta não é try/finally na mão, é using. O using declaration (sem chaves) deixa o código plano e libera no fim do escopo:

using var stream = File.OpenRead(caminho);
var hash = await SHA256.HashDataAsync(stream, ct);

Se sua classe segura um recurso descartável, ela também vira IDisposable — e repassa o Dispose pra baixo. Segurar recurso sem soltar é vazamento silencioso que só aparece em produção, sob carga, na pior hora.

Records e imutabilidade: menos surpresa, menos bug

DTO, value object, evento, mensagem: tudo isso é dado que não deveria mudar depois de criado. record te dá isso de graça — igualdade por valor, ToString decente e with pra copiar mudando um campo.

public record Cliente(string Nome, string Email);

Objeto imutável não é modificado por engano em outra thread nem em outro método três camadas abaixo. Menos estado mutável circulando é menos bug de "mas eu não mudei isso". Guarde class mutável pra quando você realmente precisa de identidade e ciclo de vida — não pra todo saco de dados.

Configuração com IOptions, não com string mágica

Espalhar configuration["ChaveDaApi"] pelo código é dívida garantida: sem tipo, sem validação, e você só descobre que a chave sumiu quando estoura em runtime. Modele a configuração numa classe e injete IOptions<T>.

builder.Services.AddOptions<EmailOptions>()
    .Bind(builder.Configuration.GetSection("Email"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

ValidateOnStart é o pulo do gato: config inválida derruba a aplicação na subida, não no meio de um envio às duas da manhã. Se a config muda em runtime, use IOptionsSnapshot. Mas comece pelo simples.

Log estruturado: pare de concatenar string

_logger.LogInformation("Pedido " + id + " criado") é um log que você não consegue consultar. Use os placeholders — eles viram campos indexados no seu backend de observabilidade:

_logger.LogInformation("Pedido {PedidoId} criado para {ClienteId}", pedido.Id, cliente.Id);

Agora você filtra por PedidoId no Seq, no Grafana, onde for. Junte isso a ILogger<T> (que já te dá a categoria certa) e a scopes pra correlacionar requests, e o log deixa de ser lixo textual e vira ferramenta de investigação.

Boa prática em .NET raramente é a coisa mais nova do NuGet. Quase sempre é usar o que a plataforma já entrega, do jeito que ela foi feita pra ser usada. O framework não vai te impedir de fazer gambiarra — mas também não te obriga. A escolha, como sempre, é sua.

Gostou do artigo?

Compartilhe com seus amigos e ajude a espalhar conhecimento!