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
varquando 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
| Área | Do ✅ | Don’t ❌ |
|---|---|---|
| Nomenclatura | Usar PascalCase em classes/propriedades e camelCase em variáveis | Usar nomes curtos e genéricos (x, obj, data) |
Interfaces sempre com prefixo I (IOrderService) | Ignorar padrão de interface ou nomes ambíguos | |
| Organização | Classes com responsabilidade única (SRP) | Classes que fazem de tudo |
| Métodos curtos e objetivos | Métodos extensos e com múltiplas responsabilidades | |
| Exceções | Usar exceções específicas e log estruturado | Engolir exceções (catch {}) ou usar Exception genérico |
| Criar exceções customizadas quando necessário | Retornar null sem explicar falha | |
| Testes Unitários | Testes 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/Await | Métodos com sufixo Async, sempre usando await | Usar .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ção | XML Docs em métodos públicos, comentários explicando o “porquê” | Comentários redundantes que repetem o código |
| Estilo de Código | Usar nameof(), var quando óbvio, constantes/enums | Usar “números mágicos” ou strings literais repetidas |
| Arquitetura | Respeitar separação de camadas (Domain, App, Infra, UI) | Acessar banco diretamente de camadas de UI |
| Usar padrões como Repository/Strategy/Factory | Acoplar regras de negócio em controllers |