Saltar al contenido principal

Boas Práticas

Esta seção define convenções e boas práticas que devem ser seguidas em todo o código da solução. O objetivo é garantir clareza, consistência, testabilidade e manutenibilidade.


🔤 Nomenclatura

  • Tabelas e colunas do banco seguem snake_case. No C#, devem ser mapeadas para PascalCase:
// Banco: pedido_item (snake_case)
[Table("pedido_item")]
public class PedidoItem
{
[Column("id")] public int Id { get; set; }
[Column("cod_pedido")] public int CodPedido { get; set; }
[Column("cod_produto")] public string CodProduto { get; set; }
}
  • Interfaces sempre com prefixo I:
public interface IOrderRepository
{
Task<Pedido> GetByIdAsync(int id);
}
  • Nomes claros e autoexplicativos:
// ❌ Ruim
var x = service.Get();

// ✅ Bom
var activeOrders = orderService.GetActiveOrders();

🧩 Organização de Classes

Cada classe deve ter responsabilidade única e métodos curtos:

// ❌ Exemplo incorreto: Classe fazendo de tudo
public class PedidoService
{
public void CriarPedido() { /* lógica de criação */ }
public void ValidarPedido() { /* lógica de validação */ }
public void GerarNotaFiscal() { /* lógica de NF */ }
public void NotificarCliente() { /* lógica de notificação */ }
}

// ✅ Correto: responsabilidades separadas
public class PedidoService { public void CriarPedido() { /* ... */ } }
public class PedidoValidator { public void Validar(Pedido pedido) { /* ... */ } }
public class NotaFiscalService { public void Gerar(Pedido pedido) { /* ... */ } }
public class NotificacaoService { public void NotificarCliente(Pedido pedido) { /* ... */ } }

⚠️ Tratamento de Exceções

Sempre usar exceções específicas e logging estruturado:

try
{
await orderRepository.SaveAsync(order);
}
catch (SqlException ex)
{
_logger.LogError(ex, "Erro ao salvar pedido {PedidoId}", order.Id);
throw new PersistenceException("Erro ao salvar pedido", ex);
}
  • Benefício: facilita rastreamento e melhora os testes unitários.

🧪 Testes Unitários

Testes validam tanto cenários de sucesso quanto falha:

[Fact]
public void Should_CreateOrder_When_DataIsValid()
{
// Arrange
var pedido = new Pedido { Id = 1, ClienteId = 10 };

// Act
var result = _pedidoService.CriarPedido(pedido);

// Assert
Assert.NotNull(result);
Assert.Equal(1, result.Id);
}

[Fact]
public void Should_ThrowException_When_PedidoIsInvalid()
{
// Arrange
var pedido = new Pedido { Id = 0 };

// Act & Assert
Assert.Throws<ValidationException>(() => _pedidoService.CriarPedido(pedido));
}

🔄 Async/Await

Métodos assíncronos sempre com sufixo Async:

// ✅ Correto
public async Task<Pedido> GetByIdAsync(int id)
{
return await _context.Pedidos.FindAsync(id);
}

// ❌ Incorreto
public Pedido GetById(int id)
{
return _context.Pedidos.FindAsync(id).Result; // pode gerar deadlock
}

🏗️ Injeção de Dependência

Usar interfaces e registrar no IoC:

// Interface
public interface IOrderService
{
Task CriarPedidoAsync(Pedido pedido);
}

// Implementação
public class OrderService : IOrderService
{
private readonly IOrderRepository _repo;

public OrderService(IOrderRepository repo) => _repo = repo;

public async Task CriarPedidoAsync(Pedido pedido) => await _repo.SaveAsync(pedido);
}

// Configuração no Startup/Extensions
services.AddScoped<IOrderService, OrderService>();

📝 Documentação de Código

Comentários devem explicar o porquê, não repetir o óbvio:

/// <summary>
/// Gera uma nova onda de picking considerando a prioridade de pedidos.
/// </summary>
/// <param name="cancellationToken">Cancelamento da operação</param>
/// <returns>Identificador da onda gerada</returns>
public async Task<int> GerarOndaAsync(CancellationToken cancellationToken)
{
// Lógica de geração da onda
}

🎨 Estilo de Código

  • Usar var quando o tipo for óbvio.
  • Usar nameof() em vez de strings mágicas.
  • Evitar números mágicos:
// ❌ Incorreto
if (status == 3) { /* ... */ }

// ✅ Correto
if (status == (int)PedidoStatus.EmProcessamento) { /* ... */ }

🏛️ Padrões Arquiteturais

  • Respeitar separação de camadas (Domain, Application, Infra, UI).
  • Utilizar padrões de projeto adequados ao contexto, como Repository, Strategy, Factory, etc..

Resumo

ÁreaDo ✅Don’t ❌
NomenclaturaUsar PascalCase em classes/propriedades e camelCase em variáveisUsar nomes curtos e genéricos (x, obj, data)
Interfaces sempre com prefixo I (IOrderService)Ignorar padrão de interface ou nomes ambíguos
OrganizaçãoClasses com responsabilidade única (SRP)Classes que fazem de tudo
Métodos curtos e objetivosMétodos extensos e com múltiplas responsabilidades
ExceçõesUsar exceções específicas e log estruturadoEngolir exceções (catch {}) ou usar Exception genérico
Criar exceções customizadas quando necessárioRetornar null sem explicar falha
Testes UnitáriosTestes claros cobrindo sucesso e falha (Assert.Throws)Testes genéricos como Test1() sem validar comportamento
Nomes expressivos (Should_X_When_Y)Testes que apenas rodam sem assertivas
Async/AwaitMétodos com sufixo Async, sempre usando awaitUsar .Result ou .Wait() → risco de deadlock
DI (Injeção)Injetar dependências via construtor + abstrações (interfaces)Instanciar dependências direto com new dentro de serviços
DocumentaçãoXML Docs em métodos públicos, comentários explicando o “porquê”Comentários redundantes que repetem o código
Estilo de CódigoUsar nameof(), var quando óbvio, constantes/enumsUsar “números mágicos” ou strings literais repetidas
ArquiteturaRespeitar separação de camadas (Domain, App, Infra, UI)Acessar banco diretamente de camadas de UI
Usar padrões como Repository/Strategy/FactoryAcoplar regras de negócio em controllers